1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-09-02 20:46:21 +02:00

Compare commits

...

89 Commits

Author SHA1 Message Date
Laurent Cozic
8eacba972e Electron release v1.0.203 2020-05-03 18:46:27 +01:00
Laurent Cozic
51732a5adb Desktop: Fixed various bugs and regressions following note editor refactoring
Squashed commit of the following:

commit 5fde36f5c3fa7c7efbce6d81f48fe841c823e88c
Author: Laurent Cozic <laurent@cozic.net>
Date:   Sun May 3 18:43:20 2020 +0100

    Cannot fix for now

commit 251284db3c8b7da6db83f3e06fd19bfdc8b8dd3f
Author: Laurent Cozic <laurent@cozic.net>
Date:   Sun May 3 18:31:08 2020 +0100

    Fixed print to multiple PDFs logic

commit 00d9557996fb984b400fe650594150ae2681e03f
Author: Laurent Cozic <laurent@cozic.net>
Date:   Sun May 3 17:49:20 2020 +0100

    Fixed local search in TinyMCE

commit 46778bf0a79f3bba9ddbc27389fadce4f8944507
Author: Laurent Cozic <laurent@cozic.net>
Date:   Sun May 3 17:37:31 2020 +0100

    Restored note toolbar buttons

commit 3e623c98f0a1cf08bf7d0136f0c8982c5e1ddcd8
Author: Laurent Cozic <laurent@cozic.net>
Date:   Sun May 3 12:30:57 2020 +0100

    Various fixes and moved note toolbar to left of title

commit 790262fe9df5b08d4a619e5244d2906047b39855
Author: Laurent Cozic <laurent@cozic.net>
Date:   Sun May 3 11:21:11 2020 +0100

    Clean up

commit cea30f42e69014ecabda6fa5083199a1ba7b7510
Author: Laurent Cozic <laurent@cozic.net>
Date:   Sun May 3 11:08:23 2020 +0100

    Fixed note loading in TinyMCE
2020-05-03 18:44:49 +01:00
Laurent Cozic
f51873d877 Desktop: Remove colour gradient from sidebar and replaced expand icons with chrevrons 2020-05-02 17:17:15 +01:00
Laurent Cozic
d738fa54ad Electron release v1.0.202 2020-05-02 16:52:38 +01:00
Laurent Cozic
ec8ccc94aa Revert "Tests: Fix random failures (#2777)"
This reverts commit d1cab4b7f5.
Part of this revert: d2582f4fdf

For rationale see https://github.com/laurent22/joplin/pull/2819#issuecomment-616148984
2020-05-02 16:45:25 +01:00
Laurent Cozic
cb8dca747b Refactor note editor
Refactor note editor using React Hooks and TypeScript
and moved editor-specific code to separate files.
Moved business logic into more maintainable custom hooks.

Squashed commit of the following:

commit f243d9bf89bdcfa1849ee26df5c0dd3e33405010
Author: Laurent Cozic <laurent@cozic.net>
Date:   Sat May 2 16:04:14 2020 +0100

    Fixed saving issue

commit 055f68d2e8b6cf6f130336c38ac2ab480887583d
Author: Laurent Cozic <laurent@cozic.net>
Date:   Sat May 2 15:43:38 2020 +0100

    Fixed HTML notes

commit 99a3cf71f58d2fedcdf3001bf4110b6e8e3993da
Merge: 9be85c45f2 b16ebbbf7a
Author: Laurent Cozic <laurent@cozic.net>
Date:   Sat May 2 12:54:42 2020 +0100

    Merge branch 'master' into refactor_note_text

commit 9be85c45f23e5cb1ecd612b0ee631947871ada6f
Author: Laurent Cozic <laurent@cozic.net>
Date:   Sat May 2 12:21:01 2020 +0100

    Ident to space

commit 848dde1869c010fe5851f493ef7287ada5f2991e
Author: Laurent Cozic <laurent@cozic.net>
Date:   Sat May 2 11:28:50 2020 +0100

    Refactor prop types

commit 13c3bbe2b4f9a522ea3f8a25e7e5e7bb026dfd4f
Author: Laurent Cozic <laurent@cozic.net>
Date:   Sat May 2 11:15:45 2020 +0100

    Fixed resource loading issue

commit 50cb38e3f00ef40ea8b6a468eadd66728a3ec332
Author: Laurent Cozic <laurent@cozic.net>
Date:   Fri May 1 23:46:58 2020 +0100

    Fixed resource loading logic

commit bc42ed03735f50c8394d597bb9e67312e55752fe
Author: Laurent Cozic <laurent@cozic.net>
Date:   Fri May 1 23:08:41 2020 +0100

    Various fixes

commit 03c038e6d6cbde03bd474798b96c4eb120fd1647
Author: Laurent Cozic <laurent@cozic.net>
Date:   Wed Apr 29 23:22:49 2020 +0100

    Fixed resource handling

commit dc6c15302fac094c4e7dec5a20c9fcc4edb3d132
Author: Laurent Cozic <laurent@cozic.net>
Date:   Wed Apr 29 22:55:13 2020 +0100

    Moved more code to files

commit 398d5121e53df34de89b4148ef2cfd3a7bbe4feb
Author: Laurent Cozic <laurent@cozic.net>
Date:   Wed Apr 29 00:22:43 2020 +0000

    More fixes

commit 3ebbb80147d7d502fd955776c7fedb743400597f
Author: Laurent Cozic <laurent@cozic.net>
Date:   Wed Apr 29 00:12:44 2020 +0000

    Various improvements and bug fixes

commit 52a65ed3875e0709117ca93ba723e20624577d05
Author: Laurent Cozic <laurent@cozic.net>
Date:   Tue Apr 28 23:51:07 2020 +0000

    Move more code to sub-files

commit 33ccf530fb442d7ddae0852cbab2c335efdbbf33
Author: Laurent Cozic <laurent@cozic.net>
Date:   Tue Apr 28 23:25:12 2020 +0100

    Moved code to sub-files

commit ba3ad2cf9fcc1d7809df4afe93cd9737585a9960
Merge: 445acdab73 150ee14de6
Author: Laurent Cozic <laurent@cozic.net>
Date:   Tue Apr 28 22:28:56 2020 +0100

    Merge branch 'master' into refactor_note_text

commit 445acdab7368345369d7f69b9becd1e77c8383dc
Author: Laurent Cozic <laurent@cozic.net>
Date:   Tue Apr 28 19:01:41 2020 +0100

    Imported more code

commit 772481d3a3ac7f0b0b00e86394c0f4fd2f3a9fa7
Author: Laurent Cozic <laurent@cozic.net>
Date:   Mon Apr 27 23:43:17 2020 +0000

    Handle save/load state

commit b3b92192ae3a1a30e3018810346cebfad47ac5e3
Author: Laurent Cozic <laurent@cozic.net>
Date:   Mon Apr 27 23:11:11 2020 +0000

    Clean up and added back scroll

commit 7a19ecfd0cb7fef1d58ece2e024099c7e40986da
Author: Laurent Cozic <laurent@cozic.net>
Date:   Mon Apr 27 22:29:39 2020 +0100

    More refactoring

commit ac388afd381eaecfa4582b3566d032c9d953c4dc
Author: Laurent Cozic <laurent@cozic.net>
Date:   Sun Apr 26 17:07:01 2020 +0100

    Restored print

commit 1d2c0ed389a5398dacc584d24922c5ea0dda861a
Author: Laurent Cozic <laurent@cozic.net>
Date:   Sun Apr 26 12:03:15 2020 +0100

    Put back search

commit c618cb59d43fa3bb507dbd0b757b302ecfe907b3
Author: Laurent Cozic <laurent@cozic.net>
Date:   Sat Apr 25 18:21:11 2020 +0100

    Restore scrolling behaviour

commit 324e6ea79ebafab1d2bca246ef030751147a47eb
Author: Laurent Cozic <laurent@cozic.net>
Date:   Sat Apr 25 10:22:31 2020 +0100

    Simplified saving notes

commit ef089aaf2289193bf275d94c1f2785f6d88657e4
Author: Laurent Cozic <laurent@cozic.net>
Date:   Sat Apr 25 10:12:16 2020 +0100

    More refactoring

commit 61b102307d5a98d2c1502d7bf073592da21af720
Author: Laurent Cozic <laurent@cozic.net>
Date:   Fri Apr 24 18:04:44 2020 +0100

    Added back note revisions

commit 7d5e3694d0df044b8493d9114e89e2d81c9b69ad
Author: Laurent Cozic <laurent@cozic.net>
Date:   Thu Apr 23 22:51:52 2020 +0000

    More note toolbar refactoring

commit a56d58e7c80d91f29afadaffaaa004f3254482f7
Author: Laurent Cozic <laurent@cozic.net>
Date:   Thu Apr 23 20:54:37 2020 +0100

    Finished toolbar refactoring

commit 6c8ef9f44f880a9569eed5c54c9c47dca2251e5e
Author: Laurent Cozic <laurent@cozic.net>
Date:   Thu Apr 23 19:17:44 2020 +0100

    More refactoring

commit 7de8057158a9256e2e0dcf948081e10a6a642216
Author: Laurent Cozic <laurent@cozic.net>
Date:   Wed Apr 22 23:48:42 2020 +0100

    Started refactoring commands

commit 177263c85e7d17d8ddc01b583738c2ab14b3acd7
Merge: f58f1a06e0 7ceb68d835
Author: Laurent Cozic <laurent@cozic.net>
Date:   Wed Apr 22 20:26:19 2020 +0100

    Merge branch 'master' into refactor_note_text

commit f58f1a06e08b3cf80e2ac7a794b15f4b5caf8932
Author: Laurent Cozic <laurent@cozic.net>
Date:   Wed Apr 22 20:25:43 2020 +0100

    Moving Ace Editor to separate component

commit a83d3a220515137985c0f334f5848c91b8539138
Author: Laurent Cozic <laurent@cozic.net>
Date:   Mon Apr 20 20:33:21 2020 +0000

    Cleaned up directory structure for note editor

commit c6f2e609c9443bac21de5033bbedf86ac6f12cc0
Author: Laurent Cozic <laurent@cozic.net>
Date:   Mon Apr 20 19:23:06 2020 +0100

    Added "note" menu to move note-related items to it

commit 1219465318ae5a7a2c777ae2ec15d3357e1499df
Author: Laurent Cozic <laurent@cozic.net>
Date:   Mon Apr 20 19:05:04 2020 +0100

    Moved note related toolbar to separate component
2020-05-02 16:41:07 +01:00
Laurent Cozic
b16ebbbf7a Tools: Fixed clipper watch tool 2020-05-02 12:52:24 +01:00
Laurent Cozic
90ccb34916 Doc: Update contributor list 2020-05-01 14:51:29 +01:00
Laurent Cozic
724af8f757 Docs: Add Season of Docs info 2020-05-01 14:50:28 +01:00
mic704b
125fa8647a All: Fix format of geolocation data (#2673)
* Maintain geolocation data format through serialization.

* Add test.
2020-04-30 16:56:47 +01:00
Helmut K. C. Tessarek
bf47237d8f Desktop: Add + to access 'Zoom In', allows to use the numpad (#2630)
* add + to access 'Zoom In', allows to use the numpad

This change aligns Joplin's behavior with all other apps that have shortcuts for `Zoom In`.
It therefore improves the user experience.

/ref

https://discourse.joplinapp.org/t/please-help-test-the-new-desktop-pre-release/6445/2?u=tessus
https://github.com/laurent22/joplin/pull/2165#issuecomment-593106648

* add comment

* improve comment
2020-04-30 10:08:19 +01:00
PackElend
294b9fc941 refreshed ideas 1 and 2, added 3 2020-04-30 00:48:52 +02:00
PackElend
699c1ab58c added sup-steps in recommended steps
Added important sub-steps under point 5 in the recommended steps to ensure that the applicants don't blow the backlog
2020-04-30 00:32:39 +02:00
Laurent Cozic
150ee14de6 Update website 2020-04-28 19:09:03 +01:00
Laurent Cozic
b207f7a53f Doc: Fixed GitHub Sponsor button 2020-04-28 19:08:31 +01:00
Laurent Cozic
096698912e Update website 2020-04-28 19:03:54 +01:00
Xaris Ar
a5ec1be7d1 All: Translation: Update el_GR.po (#3128) 2020-04-25 16:09:12 -04:00
espinosa
f9f884a7f3 All: Translation: Update fr_FR.po (#3123)
* Partial translation in French

* Remplacé "décryptage" par "déchiffrage"
2020-04-25 13:12:20 -04:00
James ADJINWA
31d59c1e21 Doc: Little typo correction in E2EE (#3122) 2020-04-25 11:14:35 +01:00
Laurent Cozic
7663044e9f Doc: Added GitHub Sponsor button 2020-04-22 23:44:48 +00:00
Rahul Mohata
7ceb68d835 Desktop: Fixes #3095: Fixed the Goto Anything item list overflow (#3096)
* fixed the Goto Anything item list overflowing

* added dialog box styles
2020-04-21 23:57:04 +01:00
Helmut K. C. Tessarek
ac2635a363 Update translations 2020-04-21 08:03:29 -04:00
Arda Kılıçdağı
2496e6e738 All: Translation: Update tr_TR.po (#3104) 2020-04-21 07:41:31 -04:00
Gen Neko
b4b36c6f22 All: Translation: Update ja_JP.po (#3105) 2020-04-21 07:39:45 -04:00
Laurent Cozic
0171ea837f Merge branch 'master' of github.com:laurent22/joplin 2020-04-21 11:17:40 +01:00
Laurent Cozic
79a343c2d9 Tools: Removed obsolete labels 2020-04-21 10:10:01 +01:00
Laurent Cozic
dd5ed48c7e Tools: Better handling of translation messages in changelog 2020-04-20 18:10:27 +00:00
Laurent Cozic
e7169cc70d Desktop: Fixes #3093: Prevent Goto Anything from freezing when inputting special characters 2020-04-20 18:01:28 +00:00
Zaid Kesarani
c65ee4a424 Desktop: Fixed KaTeX font issue in exported PDF and HTML (#3089)
* Fixed issue: #3078

* updated comments

* Changed as requested
2020-04-20 15:17:11 +01:00
Mohammed Rabeeh
feee162578 Mobile: Fixes #3098: Fixed table background colour on dark themes (#3100) 2020-04-20 15:15:31 +01:00
mic704b
d1cab4b7f5 Tests: Fix random failures (#2777)
* Update new feature tests following test harness fixes.

* Fix lint errors.
2020-04-20 11:32:42 +01:00
Laurent Cozic
d2acf314f5 Android release v1.0.329 2020-04-20 10:13:04 +01:00
Laurent Cozic
b803984773 Android: Fixes #3041: Fixed text scrolling issue on older devices 2020-04-20 22:31:21 +00:00
Laurent Cozic
d2582f4fdf Revert "Restored note history feature by Naveen M V <naveenmv7@gmail.com>"
This reverts commit 61d3582357.

For rational see https://github.com/laurent22/joplin/pull/2819#issuecomment-616148984
2020-04-19 17:14:57 +01:00
Laurent Cozic
dfd18ebe24 Android release v1.0.328 2020-04-19 16:25:38 +01:00
Laurent Cozic
94082bc6a5 Android: Fixes #3041 (maybe): Fixed scrolling issue on certain Android devices when displaying large notes 2020-04-19 16:21:09 +01:00
Laurent Cozic
5c3c72c37e Merge branch 'master' of github.com:laurent22/joplin 2020-04-19 16:02:28 +01:00
Laurent Cozic
d730e068b0 All: Fixes #3088: Better handling of missing table field bug on Linux 2020-04-19 16:02:10 +01:00
PackElend
b2f8c38842 changed gsos2020 to gsod2020 2020-04-19 12:39:06 +02:00
PackElend
71b2e36429 changed from GSoD-2020 to gsod2020 2020-04-19 12:33:50 +02:00
Laurent Cozic
e598c64241 Merge branch 'master' of github.com:laurent22/joplin 2020-04-19 10:12:04 +01:00
Laurent Cozic
2b2ec2c655 All: Started resource fetcher service when a note has been decrypted 2020-04-19 10:11:46 +01:00
Helmut K. C. Tessarek
381d757525 Update translations 2020-04-18 19:20:09 -04:00
Helmut K. C. Tessarek
d54c59cf74 fix nl_BE.po - for some reason the CI did not catch this 2020-04-18 19:19:22 -04:00
johanvanheusden
1f481fba4e All: Translation: Update nl_BE.po (#3086)
File corrected and completed until line 470
2020-04-18 19:14:16 -04:00
Lorinson
69cd88e4a5 All: Translation: Update pt_PT.po (#3018)
* PT-PT Locale Update

* PT-PT 18/04/2020 Update

* Update pt_PT.po
2020-04-18 14:40:02 -04:00
PackElend
b140a9d7de GSoD ideas
first draft of the idea summery for Google's Season of Doc 2020
2020-04-18 17:33:57 +02:00
PackElend
b1b530edf2 GSoDoc-2020 intro
Creating the first draft of the intro page for Google Season of Doc 2020
2020-04-18 17:17:20 +02:00
Laurent Cozic
ab63316f83 Merge branch 'master' of github.com:laurent22/joplin 2020-04-18 12:46:08 +01:00
Laurent Cozic
35df8e5d9e All: Added support for basic search 2020-04-18 12:45:54 +01:00
jabdoa2
ea3c82733c Add LXDE to installer as it works fine there (#3081) 2020-04-18 00:03:06 +01:00
rnbastos
60f75fe2db All: Translation: Update pt_BR.po (#3065) 2020-04-17 06:57:40 -04:00
Mohammed Rabeeh
9214095a64 iOS: Resolves #2798: Switch keyboard colour with theme (#3061) 2020-04-16 23:35:31 +01:00
Laurent Cozic
676c43ebab Doc: Added blog posts for archival purpose and to allow listing them in notifications 2020-04-15 22:11:42 +00:00
Laurent Cozic
e65af8c1e4 Electron release v1.0.201 2020-04-15 17:38:14 +01:00
Laurent Cozic
2321455723 Update stale.yml 2020-04-15 11:45:03 +01:00
Laurent Cozic
9260235224 Tools: Set stale limit to 45 days
As the project is active, 45 days is enough time to label the issue (and then it won't be closed). If it has not been labelled within that time it's probably not important enough to keep it open, or we don't have resources to deal with it.
2020-04-15 11:44:55 +01:00
Laurent Cozic
56801c0a08 Merge branch 'master' of github.com:laurent22/joplin 2020-04-15 11:24:53 +01:00
Helmut K. C. Tessarek
aa738cc086 Revert "Desktop: Add global shortcut to show/hide Joplin (#2653)"
This reverts commit 0863f0d564.
2020-04-15 05:50:26 -04:00
Laurent Cozic
900b0f3eb2 Merge branch 'master' of github.com:laurent22/joplin 2020-04-15 09:51:44 +01:00
Mohammed Rabeeh
b5a64e2c3e Doc: Fix start scripts for iOS and Android (#3062) 2020-04-15 09:51:06 +01:00
Laurent Cozic
4163d9e474 All: Fixed regression caused by commit 6164e2d8eb 2020-04-14 23:31:28 +00:00
Laurent Cozic
c9fc52ec74 Merge branch 'master' of github.com:laurent22/joplin 2020-04-14 23:58:37 +01:00
Laurent Cozic
fe72568d3b Desktop, Mobile: Fixes #3058: In some cases, notes with Katex code were not exported correctly as PDF 2020-04-14 23:58:23 +01:00
Mohammed Rabeeh
c5c379f38a Mobile: Fixes #2853: Fix white rectangle issue on dark theme (#3055) 2020-04-14 20:12:18 +01:00
Laurent Cozic
2050889590 Desktop: Fixes #2968: Trying to resource path issue in WYSIWYG editor 2020-04-13 22:55:24 +00:00
Laurent Cozic
6164e2d8eb Desktop: Search in title and body by default when using Goto Anything 2020-04-13 22:10:59 +00:00
Siddhant Sehgal
30c175ef29 Desktop: Resolves #2799: Disable prompt dialog OK button when input is empty (#2905) 2020-04-14 12:20:05 +01:00
Laurent Cozic
f6fed72b64 Desktop, Mobile: Fixes #3033: Fixed Katex rendering issue when greater than symbol appears in markup 2020-04-12 22:54:42 +00:00
Laurent Cozic
7596ff2eda Mobile, Desktop: Disable BASE html tag, which can break certain plugins such as Katex 2020-04-12 23:00:36 +01:00
Laurent Cozic
142976f012 Electron release v1.0.200 2020-04-12 12:54:25 +01:00
Laurent Cozic
ab5c97f75a Desktop: Add more log statements to attempt to fix #2968 2020-04-12 10:09:24 +01:00
Anjula Karunarathne
aea3de982a Desktop: Fixes #3028: Fix Go To Anything closing when clicking inside the modal (#3029)
* Fix Go To Anything closing when clicking inside the modal

* Update GotoAnything.jsx

* Update GotoAnything.jsx

Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2020-04-12 09:59:00 +01:00
Assim Deodia
83aff6f478 Linux: Fix install script to add support for non-default home dir (#2969)
If user home directory is configured to anything other than `/home/${USER}`, `Exec` command in desktop entry points to non-existing file, and desktop entry is not considered valid, thus does not appear in list of application. Standard way to get home directory is using `${HOME}` environment variable. This could also be possible root cause of https://discourse.joplinapp.org/t/joplin-not-installing-completely/5669/2
2020-04-12 00:29:39 +01:00
Martin Giger
6cb7154bf4 Support icon install on elementary OS (#3023) 2020-04-12 00:27:06 +01:00
Caleb John
3aa38d35d7 Desktop: Fix JEX export (#3026)
* Return empty default for exports with no note ids (jex for example)

* switch to ! syntax instead of == null
2020-04-12 00:14:53 +01:00
Siddhant Sehgal
870a76c570 Desktop: Fixes #2830: Fixes todo mouse hover behaviour (#2831)
* Fixed-todoitem-hover

* Added empty space for build fix

* Fixed-todo-hover
2020-04-11 11:38:00 +01:00
Rishabh Malhotra
206a64eff6 Desktop: Fixes #2736: Fixes selection in text editor after applying formatting (#2737)
* Appliying changes

* Update NoteText.jsx
2020-04-11 11:02:19 +01:00
Laurent Cozic
6b97839617 Merge branch 'master' of github.com:laurent22/joplin 2020-04-11 10:51:36 +01:00
Laurent Cozic
456d979aa6 Desktop: Fixes #3019: Title auto-update didn't work anymore
Revert "Desktop: Resolves #2681: Clear provisional flag as soon as note is modified to avoid data loss"

This reverts commit 518af9dc0a.
2020-04-11 10:49:16 +01:00
Ethan Chen
ea5e0d337c All: Translation: Update zh_TW.po (#3020) 2020-04-11 03:52:00 -04:00
abolishallprivateproperty
9651317695 All: Translation: Update sv.po (#3013) 2020-04-10 21:48:37 -04:00
Laurent Cozic
a4cc2076d7 Update website 2020-04-11 00:39:25 +01:00
Laurent Cozic
db820d500d Merge branch 'master' of github.com:laurent22/joplin 2020-04-11 00:38:37 +01:00
Laurent Cozic
722192127c Doc: Added info on how to fix broken installations on Windows 2020-04-11 00:36:57 +01:00
Laurent Cozic
36e23d6432 Tools: Minor update of CLI release script 2020-04-10 19:33:25 +01:00
Laurent Cozic
8cfb014f60 CLI v1.0.163 2020-04-10 19:32:20 +01:00
Laurent Cozic
b4a572c8ae ios-v10.0.47 2020-04-10 19:30:43 +01:00
Laurent Cozic
1907ef0c99 Tools: Dynamically generate PortableApps images 2020-04-10 18:19:17 +01:00
354 changed files with 32217 additions and 21803 deletions

View File

@@ -22,12 +22,13 @@ Clipper/dist
Clipper/icons
Clipper/popup/build
Clipper/popup/config/webpack.config.js
Clipper/popup/config/webpack_config_at_eject_time.js
Clipper/popup/node_modules
Clipper/popup/scripts/build.js
docs/
ElectronClient/dist
ElectronClient/gui/editors/TinyMCE/plugins/lists.js
ElectronClient/lib
ElectronClient/gui/NoteEditor/NoteBody/TinyMCE/plugins/lists.js
ElectronClient/lib/vendor/sjcl-rn.js
ElectronClient/lib/vendor/sjcl.js
ElectronClient/locales
@@ -58,15 +59,32 @@ Tools/PortableAppsLauncher
Modules/TinyMCE/IconPack/postinstall.js
# AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD
ElectronClient/gui/editors/PlainEditor.js
ElectronClient/gui/editors/TinyMCE.js
ElectronClient/gui/MultiNoteActions.js
ElectronClient/gui/NoteContentPropertiesDialog.js
ElectronClient/gui/NoteText2.js
ElectronClient/gui/NoteEditor/NoteBody/AceEditor/AceEditor.js
ElectronClient/gui/NoteEditor/NoteBody/AceEditor/styles/index.js
ElectronClient/gui/NoteEditor/NoteBody/AceEditor/Toolbar.js
ElectronClient/gui/NoteEditor/NoteBody/AceEditor/utils/index.js
ElectronClient/gui/NoteEditor/NoteBody/AceEditor/utils/types.js
ElectronClient/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.js
ElectronClient/gui/NoteEditor/NoteEditor.js
ElectronClient/gui/NoteEditor/styles/index.js
ElectronClient/gui/NoteEditor/utils/index.js
ElectronClient/gui/NoteEditor/utils/resourceHandling.js
ElectronClient/gui/NoteEditor/utils/types.js
ElectronClient/gui/NoteEditor/utils/useDropHandler.js
ElectronClient/gui/NoteEditor/utils/useFormNote.js
ElectronClient/gui/NoteEditor/utils/useMarkupToHtml.js
ElectronClient/gui/NoteEditor/utils/useMessageHandler.js
ElectronClient/gui/NoteEditor/utils/useNoteSearchBar.js
ElectronClient/gui/NoteEditor/utils/useSearchMarkers.js
ElectronClient/gui/NoteEditor/utils/useWindowCommandHandler.js
ElectronClient/gui/NoteToolbar/NoteToolbar.js
ElectronClient/gui/ResourceScreen.js
ElectronClient/gui/ShareNoteDialog.js
ElectronClient/gui/utils/NoteText.js
ReactNativeClient/lib/AsyncActionQueue.js
ReactNativeClient/lib/hooks/useImperativeHandlerDebugger.js
ReactNativeClient/lib/hooks/usePrevious.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/checkbox.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/fence.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/mermaid.js

View File

@@ -49,6 +49,7 @@ module.exports = {
"react/jsx-uses-react": "error",
"react/jsx-uses-vars": "error",
"no-unused-vars": "error",
"@typescript-eslint/no-unused-vars": "error",
"no-constant-condition": 0,
"no-prototype-builtins": 0,
// This error is always a false positive so far since it detects

6
.github/stale.yml vendored
View File

@@ -1,15 +1,11 @@
# Configuration for probot-stale - https://github.com/probot/stale
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 90
daysUntilStale: 45
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 7
# Issues with these labels will never be considered stale
exemptLabels:
- "good first issue"
- "essential"
- "essential-reviewed"
- "help wanted"
- "nice to have"
- "upstream"
- "backlog"
- "high"

25
.gitignore vendored
View File

@@ -50,15 +50,32 @@ Tools/commit_hook.txt
*.map
# AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD
ElectronClient/gui/editors/PlainEditor.js
ElectronClient/gui/editors/TinyMCE.js
ElectronClient/gui/MultiNoteActions.js
ElectronClient/gui/NoteContentPropertiesDialog.js
ElectronClient/gui/NoteText2.js
ElectronClient/gui/NoteEditor/NoteBody/AceEditor/AceEditor.js
ElectronClient/gui/NoteEditor/NoteBody/AceEditor/styles/index.js
ElectronClient/gui/NoteEditor/NoteBody/AceEditor/Toolbar.js
ElectronClient/gui/NoteEditor/NoteBody/AceEditor/utils/index.js
ElectronClient/gui/NoteEditor/NoteBody/AceEditor/utils/types.js
ElectronClient/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.js
ElectronClient/gui/NoteEditor/NoteEditor.js
ElectronClient/gui/NoteEditor/styles/index.js
ElectronClient/gui/NoteEditor/utils/index.js
ElectronClient/gui/NoteEditor/utils/resourceHandling.js
ElectronClient/gui/NoteEditor/utils/types.js
ElectronClient/gui/NoteEditor/utils/useDropHandler.js
ElectronClient/gui/NoteEditor/utils/useFormNote.js
ElectronClient/gui/NoteEditor/utils/useMarkupToHtml.js
ElectronClient/gui/NoteEditor/utils/useMessageHandler.js
ElectronClient/gui/NoteEditor/utils/useNoteSearchBar.js
ElectronClient/gui/NoteEditor/utils/useSearchMarkers.js
ElectronClient/gui/NoteEditor/utils/useWindowCommandHandler.js
ElectronClient/gui/NoteToolbar/NoteToolbar.js
ElectronClient/gui/ResourceScreen.js
ElectronClient/gui/ShareNoteDialog.js
ElectronClient/gui/utils/NoteText.js
ReactNativeClient/lib/AsyncActionQueue.js
ReactNativeClient/lib/hooks/useImperativeHandlerDebugger.js
ReactNativeClient/lib/hooks/usePrevious.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/checkbox.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/fence.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/mermaid.js

View File

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

View File

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 70 KiB

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -34,8 +34,8 @@ First you need to setup React Native to build projects with native code. For thi
Then:
cd ReactNativeClient
npm start-android
# Or: npm start-ios
npm run start-android
# Or: npm run start-ios
To run the iOS application, it might be easier to open the file `ios/Joplin.xcworkspace` on XCode and run the app from there.

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 one or more lines are too long

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 one or more lines are too long

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 one or more lines are too long

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 one or more lines are too long

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 one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -37,41 +37,41 @@ locales['tr_TR'] = require('./tr_TR.json');
locales['vi'] = require('./vi.json');
locales['zh_CN'] = require('./zh_CN.json');
locales['zh_TW'] = require('./zh_TW.json');
stats['ar'] = {"percentDone":89};
stats['ar'] = {"percentDone":87};
stats['eu'] = {"percentDone":37};
stats['bs_BA'] = {"percentDone":82};
stats['bg_BG'] = {"percentDone":74};
stats['bs_BA'] = {"percentDone":81};
stats['bg_BG'] = {"percentDone":73};
stats['ca'] = {"percentDone":58};
stats['hr_HR'] = {"percentDone":31};
stats['cs_CZ'] = {"percentDone":91};
stats['da_DK'] = {"percentDone":82};
stats['de_DE'] = {"percentDone":99};
stats['et_EE'] = {"percentDone":73};
stats['cs_CZ'] = {"percentDone":90};
stats['da_DK'] = {"percentDone":81};
stats['de_DE'] = {"percentDone":97};
stats['et_EE'] = {"percentDone":72};
stats['en_GB'] = {"percentDone":100};
stats['en_US'] = {"percentDone":100};
stats['es_ES'] = {"percentDone":91};
stats['eo'] = {"percentDone":42};
stats['fr_FR'] = {"percentDone":92};
stats['gl_ES'] = {"percentDone":48};
stats['it_IT'] = {"percentDone":97};
stats['nl_NL'] = {"percentDone":94};
stats['nl_BE'] = {"percentDone":38};
stats['nb_NO'] = {"percentDone":99};
stats['es_ES'] = {"percentDone":90};
stats['eo'] = {"percentDone":41};
stats['fr_FR'] = {"percentDone":91};
stats['gl_ES'] = {"percentDone":47};
stats['it_IT'] = {"percentDone":96};
stats['nl_NL'] = {"percentDone":92};
stats['nl_BE'] = {"percentDone":37};
stats['nb_NO'] = {"percentDone":97};
stats['fa'] = {"percentDone":36};
stats['pl_PL'] = {"percentDone":85};
stats['pt_PT'] = {"percentDone":87};
stats['pt_BR'] = {"percentDone":89};
stats['ro'] = {"percentDone":38};
stats['pl_PL'] = {"percentDone":84};
stats['pt_PT'] = {"percentDone":98};
stats['pt_BR'] = {"percentDone":98};
stats['ro'] = {"percentDone":37};
stats['sl_SI'] = {"percentDone":47};
stats['sv'] = {"percentDone":65};
stats['th_TH'] = {"percentDone":59};
stats['vi'] = {"percentDone":95};
stats['tr_TR'] = {"percentDone":98};
stats['el_GR'] = {"percentDone":90};
stats['ru_RU'] = {"percentDone":98};
stats['sr_RS'] = {"percentDone":80};
stats['zh_CN'] = {"percentDone":98};
stats['zh_TW'] = {"percentDone":88};
stats['ja_JP'] = {"percentDone":99};
stats['ko'] = {"percentDone":96};
stats['sv'] = {"percentDone":78};
stats['th_TH'] = {"percentDone":58};
stats['vi'] = {"percentDone":94};
stats['tr_TR'] = {"percentDone":100};
stats['el_GR'] = {"percentDone":89};
stats['ru_RU'] = {"percentDone":97};
stats['sr_RS'] = {"percentDone":78};
stats['zh_CN'] = {"percentDone":97};
stats['zh_TW'] = {"percentDone":98};
stats['ja_JP'] = {"percentDone":100};
stats['ko'] = {"percentDone":95};
module.exports = { locales: locales, stats: stats };

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 one or more lines are too long

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 one or more lines are too long

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 one or more lines are too long

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 one or more lines are too long

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 one or more lines are too long

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 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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "joplin",
"version": "1.0.162",
"version": "1.0.163",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@@ -27,7 +27,7 @@
],
"owner": "Laurent Cozic"
},
"version": "1.0.162",
"version": "1.0.163",
"bin": {
"joplin": "./main.js"
},

View File

@@ -1,138 +0,0 @@
require('app-module-path').addPath(__dirname);
const { asyncTest, id, ids, createNTestFolders, createNTestNotes, TestApp } = require('test-utils.js');
const { time } = require('lib/time-utils.js');
let testApp = null;
const goBackWard = (state) => {
const lastItem = state.backwardHistoryNotes[state.backwardHistoryNotes.length - 1];
testApp.dispatch({ type: 'FOLDER_AND_NOTE_SELECT', noteId: lastItem.id, folderId: lastItem.parent_id, historyAction: 'pop' });
};
const goForward = (state) => {
const lastItem = state.forwardHistoryNotes[state.forwardHistoryNotes.length - 1];
testApp.dispatch({ type: 'FOLDER_AND_NOTE_SELECT', noteId: lastItem.id, folderId: lastItem.parent_id, historyAction: 'push' });
};
describe('integration_ForwardBackwardNoteHistory', function() {
beforeEach(async (done) => {
testApp = new TestApp();
await testApp.start(['--no-welcome']);
done();
});
afterEach(async (done) => {
if (testApp !== null) await testApp.destroy();
testApp = null;
done();
});
it('should save history when navigating through notes', asyncTest(async () => {
// setup
const folders = await createNTestFolders(2);
await time.msleep(100);
const notes0 = await createNTestNotes(5, folders[0]);
// let notes1 = await createNTestNotes(5, folders[1]);
await time.msleep(100);
testApp.dispatch({ type: 'FOLDER_SELECT', id: id(folders[0]) });
await time.msleep(100);
let state = testApp.store().getState();
expect(state.backwardHistoryNotes).toEqual([]);
expect(state.forwardHistoryNotes).toEqual([]);
testApp.dispatch({ type: 'NOTE_SELECT', id: notes0[3].id, historyAction: 'goto' });
await time.msleep(100);
testApp.dispatch({ type: 'NOTE_SELECT', id: notes0[2].id, historyAction: 'goto' });
await time.msleep(100);
testApp.dispatch({ type: 'NOTE_SELECT', id: notes0[1].id, historyAction: 'goto' });
await time.msleep(100);
testApp.dispatch({ type: 'NOTE_SELECT', id: notes0[0].id, historyAction: 'goto' });
await time.msleep(100);
state = testApp.store().getState();
expect(ids(state.backwardHistoryNotes)).toEqual(ids([notes0[4], notes0[3], notes0[2], notes0[1]]));
expect(ids(state.forwardHistoryNotes)).toEqual([]);
goBackWard(state);
await time.msleep(100);
state = testApp.store().getState();
expect(ids(state.backwardHistoryNotes)).toEqual(ids([notes0[4], notes0[3], notes0[2]]));
expect(ids(state.forwardHistoryNotes)).toEqual(ids([notes0[0]]));
goBackWard(state);
await time.msleep(100);
state = testApp.store().getState();
expect(ids(state.backwardHistoryNotes)).toEqual(ids([notes0[4], notes0[3]]));
expect(ids(state.forwardHistoryNotes)).toEqual(ids([notes0[0], notes0[1]]));
goForward(state);
await time.msleep(100);
state = testApp.store().getState();
expect(ids(state.backwardHistoryNotes)).toEqual(ids([notes0[4], notes0[3], notes0[2]]));
expect(ids(state.forwardHistoryNotes)).toEqual(ids([notes0[0]]));
testApp.dispatch({ type: 'NOTE_SELECT', id: notes0[4].id, historyAction: 'goto' });
await time.msleep(100);
state = testApp.store().getState();
expect(ids(state.backwardHistoryNotes)).toEqual(ids([notes0[4], notes0[3], notes0[2], notes0[1]]));
expect(ids(state.forwardHistoryNotes)).toEqual([]);
}));
it('should save history when navigating through notebooks', asyncTest(async () => {
const folders = await createNTestFolders(2);
await time.msleep(100);
const notes0 = await createNTestNotes(5, folders[0]);
const notes1 = await createNTestNotes(5, folders[1]);
await time.msleep(100);
testApp.dispatch({ type: 'FOLDER_SELECT', id: id(folders[0]) });
await time.msleep(100);
let state = testApp.store().getState();
expect(state.backwardHistoryNotes).toEqual([]);
expect(state.forwardHistoryNotes).toEqual([]);
testApp.dispatch({ type: 'FOLDER_SELECT', id: id(folders[1]), historyAction: 'goto' });
await time.msleep(100);
state = testApp.store().getState();
expect(ids(state.backwardHistoryNotes)).toEqual(ids([notes0[4]])); // notes0[4] was last created
expect(ids(state.forwardHistoryNotes)).toEqual([]);
testApp.dispatch({ type: 'FOLDER_SELECT', id: id(folders[0]), historyAction: 'goto' });
await time.msleep(100);
state = testApp.store().getState();
expect(ids(state.backwardHistoryNotes)).toEqual(ids([notes0[4], notes1[4]]));
expect(state.forwardHistoryNotes).toEqual([]);
goBackWard(state);
await time.msleep(100);
state = testApp.store().getState();
expect(ids(state.backwardHistoryNotes)).toEqual(ids([notes0[4]]));
expect(ids(state.forwardHistoryNotes)).toEqual(ids([notes0[4]]));
goForward(state);
await time.msleep(100);
state = testApp.store().getState();
expect(ids(state.backwardHistoryNotes)).toEqual(ids([notes0[4], notes1[4]]));
expect(state.forwardHistoryNotes).toEqual([]);
}));
});

View File

@@ -69,4 +69,39 @@ describe('models_BaseItem', function() {
expect(unserialized.user_created_time).toEqual(note.user_created_time);
expect(unserialized.user_updated_time).toEqual(note.user_updated_time);
}));
it('should serialize geolocation fields', asyncTest(async () => {
const folder = await Folder.save({ title: 'folder' });
let note = await Note.save({ title: 'note', parent_id: folder.id });
note = await Note.load(note.id);
let serialized = await Note.serialize(note);
let unserialized = await Note.unserialize(serialized);
expect(unserialized.latitude).toEqual('0.00000000');
expect(unserialized.longitude).toEqual('0.00000000');
expect(unserialized.altitude).toEqual('0.0000');
await Note.updateGeolocation(note.id);
note = await Note.load(note.id);
serialized = await Note.serialize(note);
unserialized = await Note.unserialize(serialized);
expect(unserialized.latitude).toEqual(note.latitude);
expect(unserialized.longitude).toEqual(note.longitude);
expect(unserialized.altitude).toEqual(note.altitude);
}));
it('should serialize and unserialize notes', asyncTest(async () => {
const folder = await Folder.save({ title: 'folder' });
const note = await Note.save({ title: 'note', parent_id: folder.id });
await Note.updateGeolocation(note.id);
const noteBefore = await Note.load(note.id);
const serialized = await Note.serialize(noteBefore);
const noteAfter = await Note.unserialize(serialized);
expect(noteAfter).toEqual(noteBefore);
}));
});

View File

@@ -6,6 +6,7 @@ const { time } = require('lib/time-utils.js');
const { sortedIds, createNTestNotes, asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');
const Setting = require('lib/models/Setting.js');
const BaseModel = require('lib/BaseModel.js');
const ArrayUtils = require('lib/ArrayUtils.js');
const { shim } = require('lib/shim');
@@ -216,4 +217,54 @@ describe('models_Note', function() {
const hasThrown = await checkThrowAsync(async () => await Folder.copyToFolder(note1.id, folder2.id));
expect(hasThrown).toBe(true);
}));
it('should convert resource paths from internal to external paths', asyncTest(async () => {
const resourceDirName = Setting.value('resourceDirName');
const resourceDir = Setting.value('resourceDir');
const r1 = await shim.createResourceFromPath(`${__dirname}/../tests/support/photo.jpg`);
const r2 = await shim.createResourceFromPath(`${__dirname}/../tests/support/photo.jpg`);
const testCases = [
[
false,
'',
'',
],
[
true,
'',
'',
],
[
false,
`![](:/${r1.id})`,
`![](${resourceDirName}/${r1.id}.jpg)`,
],
[
false,
`![](:/${r1.id}) ![](:/${r1.id}) ![](:/${r2.id})`,
`![](${resourceDirName}/${r1.id}.jpg) ![](${resourceDirName}/${r1.id}.jpg) ![](${resourceDirName}/${r2.id}.jpg)`,
],
[
true,
`![](:/${r1.id})`,
`![](file://${resourceDir}/${r1.id}.jpg)`,
],
[
true,
`![](:/${r1.id}) ![](:/${r1.id}) ![](:/${r2.id})`,
`![](file://${resourceDir}/${r1.id}.jpg) ![](file://${resourceDir}/${r1.id}.jpg) ![](file://${resourceDir}/${r2.id}.jpg)`,
],
];
for (const testCase of testCases) {
const [useAbsolutePaths, input, expected] = testCase;
const internalToExternal = await Note.replaceResourceInternalToExternalLinks(input, { useAbsolutePaths });
expect(expected).toBe(internalToExternal);
const externalToInternal = await Note.replaceResourceExternalToInternalLinks(internalToExternal, { useAbsolutePaths });
expect(externalToInternal).toBe(input);
}
}));
});

View File

@@ -36,41 +36,6 @@ function initTestState(folders, selectedFolderIndex, notes, selectedNoteIndexes,
return state;
}
function goToNote(notes, selectedNoteIndexes, state) {
if (selectedNoteIndexes != null) {
const selectedIds = [];
for (let i = 0; i < selectedNoteIndexes.length; i++) {
selectedIds.push(notes[selectedNoteIndexes[i]].id);
}
state = reducer(state, { type: 'NOTE_SELECT', ids: selectedIds, historyAction: 'goto' });
}
return state;
}
function goBackWard(state) {
if (!state.backwardHistoryNotes.length) return state;
const lastItem = state.backwardHistoryNotes[state.backwardHistoryNotes.length - 1];
state = reducer(state, {
type: 'FOLDER_AND_NOTE_SELECT',
noteId: lastItem.id ,
folderId: lastItem.parent_id ,
historyAction: 'pop',
});
return state;
}
function goForward(state) {
if (!state.forwardHistoryNotes.length) return state;
const nextItem = state.forwardHistoryNotes[state.forwardHistoryNotes.length - 1];
state = reducer(state, {
type: 'FOLDER_AND_NOTE_SELECT',
noteId: nextItem.id ,
folderId: nextItem.parent_id ,
historyAction: 'push',
});
return state;
}
function createExpectedState(items, keepIndexes, selectedIndexes) {
const expected = { items: [], selectedIds: [] };
@@ -380,118 +345,4 @@ describe('Reducer', function() {
expect(state.selectedNoteIds).toEqual(expected.selectedIds);
}));
it('should remove deleted note from history', asyncTest(async () => {
// create 1 folder
const folders = await createNTestFolders(1);
// create 5 notes
const notes = await createNTestNotes(5, folders[0]);
// select the 1st folder and the 1st note
let state = initTestState(folders, 0, notes, [0]);
// select second note
state = goToNote(notes, [1], state);
// select third note
state = goToNote(notes, [2], state);
// select fourth note
state = goToNote(notes, [3], state);
// expect history to contain first, second and third note
expect(state.backwardHistoryNotes.length).toEqual(3);
expect(getIds(state.backwardHistoryNotes)).toEqual(getIds(notes.slice(0, 3)));
// delete third note
state = reducer(state, { type: 'NOTE_DELETE', id: notes[2].id });
// expect history to not contain third note
expect(getIds(state.backwardHistoryNotes)).not.toContain(notes[2].id);
}));
it('should remove all notes of a deleted notebook from history', asyncTest(async () => {
const folders = await createNTestFolders(2);
const notes = [];
for (let i = 0; i < folders.length; i++) {
notes.push(...await createNTestNotes(3, folders[i]));
}
let state = initTestState(folders, 0, notes.slice(0,3), [0]);
state = goToNote(notes, [1], state);
state = goToNote(notes, [2], state);
// go to second folder
state = reducer(state, { type: 'FOLDER_SELECT', id: folders[1].id, historyAction: 'goto' });
expect(getIds(state.backwardHistoryNotes)).toEqual(getIds(notes.slice(0, 3)));
// delete the first folder
state = reducer(state, { type: 'FOLDER_DELETE', id: folders[0].id });
expect(getIds(state.backwardHistoryNotes)).toEqual([]);
}));
it('should maintain history correctly when going backward and forward', asyncTest(async () => {
const folders = await createNTestFolders(2);
const notes = [];
for (let i = 0; i < folders.length; i++) {
notes.push(...await createNTestNotes(5, folders[i]));
}
let state = initTestState(folders, 0, notes.slice(0,5), [0]);
state = goToNote(notes, [1], state);
state = goToNote(notes, [2], state);
state = goToNote(notes, [3], state);
state = goToNote(notes, [4], state);
expect(getIds(state.backwardHistoryNotes)).toEqual(getIds(notes.slice(0, 4)));
state = goBackWard(state);
expect(getIds(state.backwardHistoryNotes)).toEqual(getIds(notes.slice(0,3)));
expect(getIds(state.forwardHistoryNotes)).toEqual(getIds(notes.slice(4, 5)));
state = goBackWard(state);
expect(getIds(state.backwardHistoryNotes)).toEqual(getIds(notes.slice(0,2)));
// because we push the last seen note to stack.
expect(getIds(state.forwardHistoryNotes)).toEqual(getIds([notes[4], notes[3]]));
state = goForward(state);
expect(getIds(state.backwardHistoryNotes)).toEqual(getIds(notes.slice(0,3)));
expect(getIds(state.forwardHistoryNotes)).toEqual(getIds([notes[4]]));
state = goForward(state);
expect(getIds(state.backwardHistoryNotes)).toEqual(getIds(notes.slice(0,4)));
expect(getIds(state.forwardHistoryNotes)).toEqual([]);
}));
it('should remember the last seen note of a notebook', asyncTest(async () => {
const folders = await createNTestFolders(2);
const notes = [];
for (let i = 0; i < folders.length; i++) {
notes.push(...await createNTestNotes(5, folders[i]));
}
let state = initTestState(folders, 0, notes.slice(0,5), [0]);
state = goToNote(notes, [1], state);
state = goToNote(notes, [2], state);
state = goToNote(notes, [3], state);
state = goToNote(notes, [4], state); // last seen note is notes[4]
// go to second folder
state = reducer(state, { type: 'FOLDER_SELECT', id: folders[1].id, historyAction: 'goto' });
state = goToNote(notes, [5], state);
state = goToNote(notes, [6], state);
// return to first folder
state = reducer(state, { type: 'FOLDER_SELECT', id: folders[0].id, historyAction: 'goto' });
expect(state.lastSelectedNotesIds.Folder[folders[0].id]).toEqual([notes[4].id]);
// return to second folder
state = reducer(state, { type: 'FOLDER_SELECT', id: folders[1].id, historyAction: 'goto' });
expect(state.lastSelectedNotesIds.Folder[folders[1].id]).toEqual([notes[6].id]);
}));
});

View File

@@ -92,6 +92,32 @@ describe('services_SearchEngine', function() {
expect(rows[2].id).toBe(n1.id);
}));
it('should tell where the results are found', asyncTest(async () => {
const notes = [
await Note.save({ title: 'abcd efgh', body: 'abcd' }),
await Note.save({ title: 'abcd' }),
await Note.save({ title: 'efgh', body: 'abcd' }),
];
await engine.syncTables();
const testCases = [
['abcd', ['title', 'body'], ['title'], ['body']],
['efgh', ['title'], [], ['title']],
];
for (const testCase of testCases) {
const rows = await engine.search(testCase[0]);
for (let i = 0; i < notes.length; i++) {
const row = rows.find(row => row.id === notes[i].id);
const actual = row ? row.fields.sort().join(',') : '';
const expected = testCase[i + 1].sort().join(',');
expect(expected).toBe(actual);
}
}
}));
it('should order search results by relevance (2)', asyncTest(async () => {
// 1
const n1 = await Note.save({ title: 'abcd efgh', body: 'XX abcd XX efgh' });
@@ -318,7 +344,11 @@ describe('services_SearchEngine', function() {
let rows;
const testCases = [
['did-not-match', 'did-not-match'],
// "-" is considered a word delimiter so it is stripped off
// when indexing the notes. "did-not-match" is translated to
// three word "did", "not", "match"
['did-not-match', 'did not match'],
['did-not-match', '"did-not-match"'],
['does match', 'does match'],
];
@@ -332,8 +362,20 @@ describe('services_SearchEngine', function() {
rows = await engine.search(query);
expect(rows.length).toBe(1);
await Note.delete(n.id);
}
}));
it('should allow using basic search', asyncTest(async () => {
const n1 = await Note.save({ title: '- [ ] abcd' });
const n2 = await Note.save({ title: '[ ] abcd' });
await engine.syncTables();
expect((await engine.search('"- [ ]"', { searchType: SearchEngine.SEARCH_TYPE_FTS })).length).toBe(0);
expect((await engine.search('"- [ ]"', { searchType: SearchEngine.SEARCH_TYPE_BASIC })).length).toBe(1);
expect((await engine.search('"[ ]"', { searchType: SearchEngine.SEARCH_TYPE_BASIC })).length).toBe(2);
}));
});

View File

@@ -143,6 +143,7 @@ async function switchClient(id) {
Resource.encryptionService_ = encryptionServices_[id];
BaseItem.revisionService_ = revisionServices_[id];
Setting.setConstant('resourceDirName', resourceDirName(id));
Setting.setConstant('resourceDir', resourceDir(id));
await Setting.load();
@@ -213,9 +214,14 @@ async function setupDatabase(id = null) {
if (!Setting.value('clientId')) Setting.setValue('clientId', uuid.create());
}
function resourceDirName(id = null) {
if (id === null) id = currentClient_;
return `resources-${id}`;
}
function resourceDir(id = null) {
if (id === null) id = currentClient_;
return `${__dirname}/data/resources-${id}`;
return `${__dirname}/data/${resourceDirName(id)}`;
}
async function setupDatabaseAndSynchronizer(id = null) {

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,676 @@
'use strict';
const fs = require('fs');
const path = require('path');
const webpack = require('webpack');
const resolve = require('resolve');
const PnpWebpackPlugin = require('pnp-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
const InlineChunkHtmlPlugin = require('react-dev-utils/InlineChunkHtmlPlugin');
const TerserPlugin = require('terser-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const safePostCssParser = require('postcss-safe-parser');
const ManifestPlugin = require('webpack-manifest-plugin');
const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin');
const WorkboxWebpackPlugin = require('workbox-webpack-plugin');
const WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin');
const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');
const getCSSModuleLocalIdent = require('react-dev-utils/getCSSModuleLocalIdent');
const paths = require('./paths');
const modules = require('./modules');
const getClientEnvironment = require('./env');
const ModuleNotFoundPlugin = require('react-dev-utils/ModuleNotFoundPlugin');
const ForkTsCheckerWebpackPlugin = require('react-dev-utils/ForkTsCheckerWebpackPlugin');
const typescriptFormatter = require('react-dev-utils/typescriptFormatter');
const postcssNormalize = require('postcss-normalize');
const appPackageJson = require(paths.appPackageJson);
// Source maps are resource heavy and can cause out of memory issue for large source files.
const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== 'false';
// Some apps do not need the benefits of saving a web request, so not inlining the chunk
// makes for a smoother build process.
const shouldInlineRuntimeChunk = process.env.INLINE_RUNTIME_CHUNK !== 'false';
const imageInlineSizeLimit = parseInt(
process.env.IMAGE_INLINE_SIZE_LIMIT || '10000'
);
// Check if TypeScript is setup
const useTypeScript = fs.existsSync(paths.appTsConfig);
// style files regexes
const cssRegex = /\.css$/;
const cssModuleRegex = /\.module\.css$/;
const sassRegex = /\.(scss|sass)$/;
const sassModuleRegex = /\.module\.(scss|sass)$/;
// This is the production and development configuration.
// It is focused on developer experience, fast rebuilds, and a minimal bundle.
module.exports = function(webpackEnv) {
const isEnvDevelopment = webpackEnv === 'development';
const isEnvProduction = webpackEnv === 'production';
// Variable used for enabling profiling in Production
// passed into alias object. Uses a flag if passed into the build command
const isEnvProductionProfile =
isEnvProduction && process.argv.includes('--profile');
// Webpack uses `publicPath` to determine where the app is being served from.
// It requires a trailing slash, or the file assets will get an incorrect path.
// In development, we always serve from the root. This makes config easier.
const publicPath = isEnvProduction
? paths.servedPath
: isEnvDevelopment && '/';
// Some apps do not use client-side routing with pushState.
// For these, "homepage" can be set to "." to enable relative asset paths.
const shouldUseRelativeAssetPaths = publicPath === './';
// `publicUrl` is just like `publicPath`, but we will provide it to our app
// as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript.
// Omit trailing slash as %PUBLIC_URL%/xyz looks better than %PUBLIC_URL%xyz.
const publicUrl = isEnvProduction
? publicPath.slice(0, -1)
: isEnvDevelopment && '';
// Get environment variables to inject into our app.
const env = getClientEnvironment(publicUrl);
// common function to get style loaders
const getStyleLoaders = (cssOptions, preProcessor) => {
const loaders = [
isEnvDevelopment && require.resolve('style-loader'),
isEnvProduction && {
loader: MiniCssExtractPlugin.loader,
options: shouldUseRelativeAssetPaths ? { publicPath: '../../' } : {},
},
{
loader: require.resolve('css-loader'),
options: cssOptions,
},
{
// Options for PostCSS as we reference these options twice
// Adds vendor prefixing based on your specified browser support in
// package.json
loader: require.resolve('postcss-loader'),
options: {
// Necessary for external CSS imports to work
// https://github.com/facebook/create-react-app/issues/2677
ident: 'postcss',
plugins: () => [
require('postcss-flexbugs-fixes'),
require('postcss-preset-env')({
autoprefixer: {
flexbox: 'no-2009',
},
stage: 3,
}),
// Adds PostCSS Normalize as the reset css with default options,
// so that it honors browserslist config in package.json
// which in turn let's users customize the target behavior as per their needs.
postcssNormalize(),
],
sourceMap: isEnvProduction && shouldUseSourceMap,
},
},
].filter(Boolean);
if (preProcessor) {
loaders.push(
{
loader: require.resolve('resolve-url-loader'),
options: {
sourceMap: isEnvProduction && shouldUseSourceMap,
},
},
{
loader: require.resolve(preProcessor),
options: {
sourceMap: true,
},
}
);
}
return loaders;
};
return {
mode: isEnvProduction ? 'production' : isEnvDevelopment && 'development',
// Stop compilation early in production
bail: isEnvProduction,
devtool: isEnvProduction
? shouldUseSourceMap
? 'source-map'
: false
: isEnvDevelopment && 'cheap-module-source-map',
// These are the "entry points" to our application.
// This means they will be the "root" imports that are included in JS bundle.
entry: [
// Include an alternative client for WebpackDevServer. A client's job is to
// connect to WebpackDevServer by a socket and get notified about changes.
// When you save a file, the client will either apply hot updates (in case
// of CSS changes), or refresh the page (in case of JS changes). When you
// make a syntax error, this client will display a syntax error overlay.
// Note: instead of the default WebpackDevServer client, we use a custom one
// to bring better experience for Create React App users. You can replace
// the line below with these two lines if you prefer the stock client:
// require.resolve('webpack-dev-server/client') + '?/',
// require.resolve('webpack/hot/dev-server'),
isEnvDevelopment &&
require.resolve('react-dev-utils/webpackHotDevClient'),
// Finally, this is your app's code:
paths.appIndexJs,
// We include the app code last so that if there is a runtime error during
// initialization, it doesn't blow up the WebpackDevServer client, and
// changing JS code would still trigger a refresh.
].filter(Boolean),
output: {
// The build folder.
path: isEnvProduction ? paths.appBuild : undefined,
// Add /* filename */ comments to generated require()s in the output.
pathinfo: isEnvDevelopment,
// There will be one main bundle, and one file per asynchronous chunk.
// In development, it does not produce real files.
filename: isEnvProduction
? 'static/js/[name].[contenthash:8].js'
: isEnvDevelopment && 'static/js/bundle.js',
// TODO: remove this when upgrading to webpack 5
futureEmitAssets: true,
// There are also additional JS chunk files if you use code splitting.
chunkFilename: isEnvProduction
? 'static/js/[name].[contenthash:8].chunk.js'
: isEnvDevelopment && 'static/js/[name].chunk.js',
// We inferred the "public path" (such as / or /my-project) from homepage.
// We use "/" in development.
publicPath: publicPath,
// Point sourcemap entries to original disk location (format as URL on Windows)
devtoolModuleFilenameTemplate: isEnvProduction
? info =>
path
.relative(paths.appSrc, info.absoluteResourcePath)
.replace(/\\/g, '/')
: isEnvDevelopment &&
(info => path.resolve(info.absoluteResourcePath).replace(/\\/g, '/')),
// Prevents conflicts when multiple Webpack runtimes (from different apps)
// are used on the same page.
jsonpFunction: `webpackJsonp${appPackageJson.name}`,
// this defaults to 'window', but by setting it to 'this' then
// module chunks which are built will work in web workers as well.
globalObject: 'this',
},
optimization: {
minimize: isEnvProduction,
minimizer: [
// This is only used in production mode
new TerserPlugin({
terserOptions: {
parse: {
// We want terser to parse ecma 8 code. However, we don't want it
// to apply any minification steps that turns valid ecma 5 code
// into invalid ecma 5 code. This is why the 'compress' and 'output'
// sections only apply transformations that are ecma 5 safe
// https://github.com/facebook/create-react-app/pull/4234
ecma: 8,
},
compress: {
ecma: 5,
warnings: false,
// Disabled because of an issue with Uglify breaking seemingly valid code:
// https://github.com/facebook/create-react-app/issues/2376
// Pending further investigation:
// https://github.com/mishoo/UglifyJS2/issues/2011
comparisons: false,
// Disabled because of an issue with Terser breaking valid code:
// https://github.com/facebook/create-react-app/issues/5250
// Pending further investigation:
// https://github.com/terser-js/terser/issues/120
inline: 2,
},
mangle: {
safari10: true,
},
// Added for profiling in devtools
keep_classnames: isEnvProductionProfile,
keep_fnames: isEnvProductionProfile,
output: {
ecma: 5,
comments: false,
// Turned on because emoji and regex is not minified properly using default
// https://github.com/facebook/create-react-app/issues/2488
ascii_only: true,
},
},
sourceMap: shouldUseSourceMap,
}),
// This is only used in production mode
new OptimizeCSSAssetsPlugin({
cssProcessorOptions: {
parser: safePostCssParser,
map: shouldUseSourceMap
? {
// `inline: false` forces the sourcemap to be output into a
// separate file
inline: false,
// `annotation: true` appends the sourceMappingURL to the end of
// the css file, helping the browser find the sourcemap
annotation: true,
}
: false,
},
cssProcessorPluginOptions: {
preset: ['default', { minifyFontValues: { removeQuotes: false } }]
}
}),
],
// Automatically split vendor and commons
// https://twitter.com/wSokra/status/969633336732905474
// https://medium.com/webpack/webpack-4-code-splitting-chunk-graph-and-the-splitchunks-optimization-be739a861366
splitChunks: {
chunks: 'all',
name: false,
},
// Keep the runtime chunk separated to enable long term caching
// https://twitter.com/wSokra/status/969679223278505985
// https://github.com/facebook/create-react-app/issues/5358
runtimeChunk: {
name: entrypoint => `runtime-${entrypoint.name}`,
},
},
resolve: {
// This allows you to set a fallback for where Webpack should look for modules.
// We placed these paths second because we want `node_modules` to "win"
// if there are any conflicts. This matches Node resolution mechanism.
// https://github.com/facebook/create-react-app/issues/253
modules: ['node_modules', paths.appNodeModules].concat(
modules.additionalModulePaths || []
),
// These are the reasonable defaults supported by the Node ecosystem.
// We also include JSX as a common component filename extension to support
// some tools, although we do not recommend using it, see:
// https://github.com/facebook/create-react-app/issues/290
// `web` extension prefixes have been added for better support
// for React Native Web.
extensions: paths.moduleFileExtensions
.map(ext => `.${ext}`)
.filter(ext => useTypeScript || !ext.includes('ts')),
alias: {
// Support React Native Web
// https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/
'react-native': 'react-native-web',
// Allows for better profiling with ReactDevTools
...(isEnvProductionProfile && {
'react-dom$': 'react-dom/profiling',
'scheduler/tracing': 'scheduler/tracing-profiling',
}),
...(modules.webpackAliases || {}),
},
plugins: [
// Adds support for installing with Plug'n'Play, leading to faster installs and adding
// guards against forgotten dependencies and such.
PnpWebpackPlugin,
// Prevents users from importing files from outside of src/ (or node_modules/).
// This often causes confusion because we only process files within src/ with babel.
// To fix this, we prevent you from importing files out of src/ -- if you'd like to,
// please link the files into your node_modules/ and let module-resolution kick in.
// Make sure your source files are compiled, as they will not be processed in any way.
new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson]),
],
},
resolveLoader: {
plugins: [
// Also related to Plug'n'Play, but this time it tells Webpack to load its loaders
// from the current package.
PnpWebpackPlugin.moduleLoader(module),
],
},
module: {
strictExportPresence: true,
rules: [
// Disable require.ensure as it's not a standard language feature.
{ parser: { requireEnsure: false } },
// First, run the linter.
// It's important to do this before Babel processes the JS.
{
test: /\.(js|mjs|jsx|ts|tsx)$/,
enforce: 'pre',
use: [
{
options: {
cache: true,
formatter: require.resolve('react-dev-utils/eslintFormatter'),
eslintPath: require.resolve('eslint'),
resolvePluginsRelativeTo: __dirname,
},
loader: require.resolve('eslint-loader'),
},
],
include: paths.appSrc,
},
{
// "oneOf" will traverse all following loaders until one will
// match the requirements. When no loader matches it will fall
// back to the "file" loader at the end of the loader list.
oneOf: [
// "url" loader works like "file" loader except that it embeds assets
// smaller than specified limit in bytes as data URLs to avoid requests.
// A missing `test` is equivalent to a match.
{
test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
loader: require.resolve('url-loader'),
options: {
limit: imageInlineSizeLimit,
name: 'static/media/[name].[hash:8].[ext]',
},
},
// Process application JS with Babel.
// The preset includes JSX, Flow, TypeScript, and some ESnext features.
{
test: /\.(js|mjs|jsx|ts|tsx)$/,
include: paths.appSrc,
loader: require.resolve('babel-loader'),
options: {
customize: require.resolve(
'babel-preset-react-app/webpack-overrides'
),
plugins: [
[
require.resolve('babel-plugin-named-asset-import'),
{
loaderMap: {
svg: {
ReactComponent:
'@svgr/webpack?-svgo,+titleProp,+ref![path]',
},
},
},
],
],
// This is a feature of `babel-loader` for webpack (not Babel itself).
// It enables caching results in ./node_modules/.cache/babel-loader/
// directory for faster rebuilds.
cacheDirectory: true,
// See #6846 for context on why cacheCompression is disabled
cacheCompression: false,
compact: isEnvProduction,
},
},
// Process any JS outside of the app with Babel.
// Unlike the application JS, we only compile the standard ES features.
{
test: /\.(js|mjs)$/,
exclude: /@babel(?:\/|\\{1,2})runtime/,
loader: require.resolve('babel-loader'),
options: {
babelrc: false,
configFile: false,
compact: false,
presets: [
[
require.resolve('babel-preset-react-app/dependencies'),
{ helpers: true },
],
],
cacheDirectory: true,
// See #6846 for context on why cacheCompression is disabled
cacheCompression: false,
// Babel sourcemaps are needed for debugging into node_modules
// code. Without the options below, debuggers like VSCode
// show incorrect code and set breakpoints on the wrong lines.
sourceMaps: shouldUseSourceMap,
inputSourceMap: shouldUseSourceMap,
},
},
// "postcss" loader applies autoprefixer to our CSS.
// "css" loader resolves paths in CSS and adds assets as dependencies.
// "style" loader turns CSS into JS modules that inject <style> tags.
// In production, we use MiniCSSExtractPlugin to extract that CSS
// to a file, but in development "style" loader enables hot editing
// of CSS.
// By default we support CSS Modules with the extension .module.css
{
test: cssRegex,
exclude: cssModuleRegex,
use: getStyleLoaders({
importLoaders: 1,
sourceMap: isEnvProduction && shouldUseSourceMap,
}),
// Don't consider CSS imports dead code even if the
// containing package claims to have no side effects.
// Remove this when webpack adds a warning or an error for this.
// See https://github.com/webpack/webpack/issues/6571
sideEffects: true,
},
// Adds support for CSS Modules (https://github.com/css-modules/css-modules)
// using the extension .module.css
{
test: cssModuleRegex,
use: getStyleLoaders({
importLoaders: 1,
sourceMap: isEnvProduction && shouldUseSourceMap,
modules: {
getLocalIdent: getCSSModuleLocalIdent,
},
}),
},
// Opt-in support for SASS (using .scss or .sass extensions).
// By default we support SASS Modules with the
// extensions .module.scss or .module.sass
{
test: sassRegex,
exclude: sassModuleRegex,
use: getStyleLoaders(
{
importLoaders: 3,
sourceMap: isEnvProduction && shouldUseSourceMap,
},
'sass-loader'
),
// Don't consider CSS imports dead code even if the
// containing package claims to have no side effects.
// Remove this when webpack adds a warning or an error for this.
// See https://github.com/webpack/webpack/issues/6571
sideEffects: true,
},
// Adds support for CSS Modules, but using SASS
// using the extension .module.scss or .module.sass
{
test: sassModuleRegex,
use: getStyleLoaders(
{
importLoaders: 3,
sourceMap: isEnvProduction && shouldUseSourceMap,
modules: {
getLocalIdent: getCSSModuleLocalIdent,
},
},
'sass-loader'
),
},
// "file" loader makes sure those assets get served by WebpackDevServer.
// When you `import` an asset, you get its (virtual) filename.
// In production, they would get copied to the `build` folder.
// This loader doesn't use a "test" so it will catch all modules
// that fall through the other loaders.
{
loader: require.resolve('file-loader'),
// Exclude `js` files to keep "css" loader working as it injects
// its runtime that would otherwise be processed through "file" loader.
// Also exclude `html` and `json` extensions so they get processed
// by webpacks internal loaders.
exclude: [/\.(js|mjs|jsx|ts|tsx)$/, /\.html$/, /\.json$/],
options: {
name: 'static/media/[name].[hash:8].[ext]',
},
},
// ** STOP ** Are you adding a new loader?
// Make sure to add the new loader(s) before the "file" loader.
],
},
],
},
plugins: [
// Generates an `index.html` file with the <script> injected.
new HtmlWebpackPlugin(
Object.assign(
{},
{
inject: true,
template: paths.appHtml,
},
isEnvProduction
? {
minify: {
removeComments: true,
collapseWhitespace: true,
removeRedundantAttributes: true,
useShortDoctype: true,
removeEmptyAttributes: true,
removeStyleLinkTypeAttributes: true,
keepClosingSlash: true,
minifyJS: true,
minifyCSS: true,
minifyURLs: true,
},
}
: undefined
)
),
// Inlines the webpack runtime script. This script is too small to warrant
// a network request.
// https://github.com/facebook/create-react-app/issues/5358
isEnvProduction &&
shouldInlineRuntimeChunk &&
new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [/runtime-.+[.]js/]),
// Makes some environment variables available in index.html.
// The public URL is available as %PUBLIC_URL% in index.html, e.g.:
// <link rel="icon" href="%PUBLIC_URL%/favicon.ico">
// In production, it will be an empty string unless you specify "homepage"
// in `package.json`, in which case it will be the pathname of that URL.
// In development, this will be an empty string.
new InterpolateHtmlPlugin(HtmlWebpackPlugin, env.raw),
// This gives some necessary context to module not found errors, such as
// the requesting resource.
new ModuleNotFoundPlugin(paths.appPath),
// Makes some environment variables available to the JS code, for example:
// if (process.env.NODE_ENV === 'production') { ... }. See `./env.js`.
// It is absolutely essential that NODE_ENV is set to production
// during a production build.
// Otherwise React will be compiled in the very slow development mode.
new webpack.DefinePlugin(env.stringified),
// This is necessary to emit hot updates (currently CSS only):
isEnvDevelopment && new webpack.HotModuleReplacementPlugin(),
// Watcher doesn't work well if you mistype casing in a path so we use
// a plugin that prints an error when you attempt to do this.
// See https://github.com/facebook/create-react-app/issues/240
isEnvDevelopment && new CaseSensitivePathsPlugin(),
// If you require a missing module and then `npm install` it, you still have
// to restart the development server for Webpack to discover it. This plugin
// makes the discovery automatic so you don't have to restart.
// See https://github.com/facebook/create-react-app/issues/186
isEnvDevelopment &&
new WatchMissingNodeModulesPlugin(paths.appNodeModules),
isEnvProduction &&
new MiniCssExtractPlugin({
// Options similar to the same options in webpackOptions.output
// both options are optional
filename: 'static/css/[name].[contenthash:8].css',
chunkFilename: 'static/css/[name].[contenthash:8].chunk.css',
}),
// Generate an asset manifest file with the following content:
// - "files" key: Mapping of all asset filenames to their corresponding
// output file so that tools can pick it up without having to parse
// `index.html`
// - "entrypoints" key: Array of files which are included in `index.html`,
// can be used to reconstruct the HTML if necessary
new ManifestPlugin({
fileName: 'asset-manifest.json',
publicPath: publicPath,
generate: (seed, files, entrypoints) => {
const manifestFiles = files.reduce((manifest, file) => {
manifest[file.name] = file.path;
return manifest;
}, seed);
const entrypointFiles = entrypoints.main.filter(
fileName => !fileName.endsWith('.map')
);
return {
files: manifestFiles,
entrypoints: entrypointFiles,
};
},
}),
// Moment.js is an extremely popular library that bundles large locale files
// by default due to how Webpack interprets its code. This is a practical
// solution that requires the user to opt into importing specific locales.
// https://github.com/jmblog/how-to-optimize-momentjs-with-webpack
// You can remove this if you don't use Moment.js:
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
// Generate a service worker script that will precache, and keep up to date,
// the HTML & assets that are part of the Webpack build.
isEnvProduction &&
new WorkboxWebpackPlugin.GenerateSW({
clientsClaim: true,
exclude: [/\.map$/, /asset-manifest\.json$/],
importWorkboxFrom: 'cdn',
navigateFallback: publicUrl + '/index.html',
navigateFallbackBlacklist: [
// Exclude URLs starting with /_, as they're likely an API call
new RegExp('^/_'),
// Exclude any URLs whose last part seems to be a file extension
// as they're likely a resource and not a SPA route.
// URLs containing a "?" character won't be blacklisted as they're likely
// a route with query params (e.g. auth callbacks).
new RegExp('/[^/?]+\\.[^/]+$'),
],
}),
// TypeScript type checking
useTypeScript &&
new ForkTsCheckerWebpackPlugin({
typescript: resolve.sync('typescript', {
basedir: paths.appNodeModules,
}),
async: isEnvDevelopment,
useTypescriptIncrementalApi: true,
checkSyntacticErrors: true,
resolveModuleNameModule: process.versions.pnp
? `${__dirname}/pnpTs.js`
: undefined,
resolveTypeReferenceDirectiveModule: process.versions.pnp
? `${__dirname}/pnpTs.js`
: undefined,
tsconfig: paths.appTsConfig,
reportFiles: [
'**',
'!**/__tests__/**',
'!**/?(*.)(spec|test).*',
'!**/src/setupProxy.*',
'!**/src/setupTests.*',
],
silent: true,
// The formatter is invoked directly in WebpackDevServerUtils during development
formatter: isEnvProduction ? typescriptFormatter : undefined,
}),
].filter(Boolean),
// Some libraries import Node modules but don't use them in the browser.
// Tell Webpack to provide empty mocks for them so importing them works.
node: {
module: 'empty',
dgram: 'empty',
dns: 'mock',
fs: 'empty',
http2: 'empty',
net: 'empty',
tls: 'empty',
child_process: 'empty',
},
// Turn off performance processing because we utilize
// our own hints via the FileSizeReporter
performance: false,
};
};

File diff suppressed because it is too large Load Diff

View File

@@ -62,7 +62,8 @@
},
"devDependencies": {
"cra-build-watch": "^3.2.0",
"fs-extra": "^6.0.1"
"fs-extra": "^6.0.1",
"react-scripts": "^3.3.1"
},
"browserslist": [
">0.2%",

View File

@@ -1,4 +1,4 @@
const { BrowserWindow, Tray, screen, globalShortcut } = require('electron');
const { BrowserWindow, Tray, screen } = require('electron');
const { shim } = require('lib/shim');
const url = require('url');
const path = require('path');
@@ -277,15 +277,6 @@ class ElectronAppWrapper {
return false;
}
toggleWindowVisilibity() {
if (this.win_.isVisible()) {
this.win_.hide();
} else {
this.win_.setVisibleOnAllWorkspaces(true);
this.win_.show();
}
}
async start() {
// Since we are doing other async things before creating the window, we might miss
// the "ready" event. So we use the function below to make sure that the app is ready.
@@ -296,17 +287,8 @@ class ElectronAppWrapper {
this.createWindow();
const registerGlobalShortcut = globalShortcut.register('CommandOrControl+Alt+J', () => {
this.toggleWindowVisilibity();
});
if (!registerGlobalShortcut) {
console.warn('Could not register global shortcut');
}
this.electronApp_.on('before-quit', () => {
this.willQuitApp_ = true;
globalShortcut.unregisterAll();
});
this.electronApp_.on('window-all-closed', () => {

View File

@@ -3,7 +3,6 @@ const { bridge } = require('electron').remote.require('./bridge');
const InteropService = require('lib/services/InteropService');
const Setting = require('lib/models/Setting');
const Note = require('lib/models/Note.js');
const Folder = require('lib/models/Folder.js');
const { friendlySafeFilename } = require('lib/path-utils');
const md5 = require('md5');
const url = require('url');
@@ -52,27 +51,40 @@ class InteropServiceHelper {
win = bridge().newBrowserWindow(windowOptions);
return new Promise((resolve, reject) => {
win.webContents.on('did-finish-load', async () => {
win.webContents.on('did-finish-load', () => {
if (target === 'pdf') {
try {
const data = await win.webContents.printToPDF(options);
resolve(data);
} catch (error) {
reject(error);
} finally {
cleanup();
// did-finish-load will trigger when most assets are done loading, probably
// images, JavaScript and CSS. However it seems it might trigger *before*
// all fonts are loaded, which will break for example Katex rendering.
// So we need to add an additional timer to make sure fonts are loaded
// as it doesn't seem there's any easy way to figure that out.
setTimeout(async () => {
if (target === 'pdf') {
try {
const data = await win.webContents.printToPDF(options);
resolve(data);
} catch (error) {
reject(error);
} finally {
cleanup();
}
} else {
// TODO: it is crashing at this point :(
// Appears to be a Chromium bug: https://github.com/electron/electron/issues/19946
// Maybe can be fixed by doing everything from main process?
// i.e. creating a function `print()` that takes the `htmlFile` variable as input.
win.webContents.print(options, (success, reason) => {
// TODO: This is correct but broken in Electron 4. Need to upgrade to 5+
// It calls the callback right away with "false" even if the document hasn't be print yet.
cleanup();
if (!success && reason !== 'cancelled') reject(new Error(`Could not print: ${reason}`));
resolve();
});
}
} else {
win.webContents.print(options, (success, reason) => {
// TODO: This is correct but broken in Electron 4. Need to upgrade to 5+
// It calls the callback right away with "false" even if the document hasn't be print yet.
}, 2000);
cleanup();
if (!success && reason !== 'cancelled') reject(new Error(`Could not print: ${reason}`));
resolve();
});
}
});
win.loadURL(url.format({
@@ -95,27 +107,12 @@ class InteropServiceHelper {
return this.exportNoteTo_('printer', noteId, options);
}
static async defaultFilename(noteIds, fileExtension) {
const note = await Note.load(noteIds[0]);
static async defaultFilename(noteId, fileExtension) {
if (!noteId) return '';
const note = await Note.load(noteId);
// In a rare case the passed not will be null, use the id for filename
if (note === null) {
const filename = friendlySafeFilename(noteIds[0], 100);
return `${filename}.${fileExtension}`;
}
const folder = await Folder.load(note.parent_id);
const filename = friendlySafeFilename(note.title, 100);
// In a less rare case the folder will be null, just ignore it
if (folder === null) {
return `${filename}.${fileExtension}`;
}
const foldername = friendlySafeFilename(folder.title, 100);
// friendlySafeFilename assumes that the file extension is added after
return `${foldername} - ${filename}.${fileExtension}`;
const filename = friendlySafeFilename(note ? note.title : noteId, 100);
return `${filename}.${fileExtension}`;
}
static async export(dispatch, module, options = null) {
@@ -124,9 +121,10 @@ class InteropServiceHelper {
let path = null;
if (module.target === 'file') {
const noteId = options.sourceNoteIds && options.sourceNoteIds.length ? options.sourceNoteIds[0] : null;
path = bridge().showSaveDialog({
filters: [{ name: module.description, extensions: module.fileExtensions }],
defaultPath: await this.defaultFilename(options.sourceNoteIds, module.fileExtensions[0]),
defaultPath: await this.defaultFilename(noteId, module.fileExtensions[0]),
});
} else {
path = bridge().showOpenDialog({

View File

@@ -134,8 +134,6 @@ class Application extends BaseApplication {
paneOptions = ['editor', 'both'];
} else if (state.settings.layoutButtonSequence === Setting.LAYOUT_VIEWER_SPLIT) {
paneOptions = ['viewer', 'both'];
} else if (state.settings.layoutButtonSequence === Setting.LAYOUT_SPLIT_WYSIWYG) {
paneOptions = ['both', 'wysiwyg'];
} else {
paneOptions = ['editor', 'viewer', 'both'];
}
@@ -547,6 +545,7 @@ class Application extends BaseApplication {
this.dispatch({
type: 'WINDOW_COMMAND',
name: 'print',
noteIds: this.store().getState().selectedNoteIds,
});
},
};
@@ -890,33 +889,6 @@ class Application extends BaseApplication {
}, {
type: 'separator',
screens: ['Main'],
}, {
id: 'edit:commandStartExternalEditing',
label: _('Edit in external editor'),
screens: ['Main'],
accelerator: 'CommandOrControl+E',
click: () => {
this.dispatch({
type: 'WINDOW_COMMAND',
name: 'commandStartExternalEditing',
});
},
}, {
id: 'edit:setTags',
label: _('Tags'),
screens: ['Main'],
accelerator: 'CommandOrControl+Alt+T',
click: () => {
const selectedNoteIds = this.store().getState().selectedNoteIds;
this.dispatch({
type: 'WINDOW_COMMAND',
name: 'setTags',
noteIds: selectedNoteIds,
});
},
}, {
type: 'separator',
screens: ['Main'],
}, {
id: 'edit:focusSearch',
label: _('Search in all the notes'),
@@ -1031,10 +1003,22 @@ class Application extends BaseApplication {
},
accelerator: 'CommandOrControl+0',
}, {
// There are 2 shortcuts for the action 'zoom in', mainly to increase the user experience.
// Most applications handle this the same way. These applications indicate Ctrl +, but actually mean Ctrl =.
// In fact they allow both: + and =. On the English keyboard layout - and = are used without the shift key.
// So to use Ctrl + would mean to use the shift key, but this is not the case in any of the apps that show Ctrl +.
// Additionally it allows the use of the plus key on the numpad.
label: _('Zoom In'),
click: () => {
Setting.incValue('windowContentZoomFactor', 10);
},
accelerator: 'CommandOrControl+Plus',
}, {
label: _('Zoom In'),
visible: false,
click: () => {
Setting.incValue('windowContentZoomFactor', 10);
},
accelerator: 'CommandOrControl+=',
}, {
label: _('Zoom Out'),
@@ -1044,6 +1028,46 @@ class Application extends BaseApplication {
accelerator: 'CommandOrControl+-',
}],
},
note: {
label: _('&Note'),
submenu: [{
id: 'edit:commandStartExternalEditing',
label: _('Edit in external editor'),
screens: ['Main'],
accelerator: 'CommandOrControl+E',
click: () => {
this.dispatch({
type: 'WINDOW_COMMAND',
name: 'commandStartExternalEditing',
});
},
}, {
id: 'edit:setTags',
label: _('Tags'),
screens: ['Main'],
accelerator: 'CommandOrControl+Alt+T',
click: () => {
const selectedNoteIds = this.store().getState().selectedNoteIds;
this.dispatch({
type: 'WINDOW_COMMAND',
name: 'setTags',
noteIds: selectedNoteIds,
});
},
}, {
type: 'separator',
screens: ['Main'],
}, {
label: _('Statistics...'),
click: () => {
this.dispatch({
type: 'WINDOW_COMMAND',
name: 'commandContentProperties',
// text: this.state.note.body,
});
},
}],
},
tools: {
label: _('&Tools'),
submenu: toolsItems,
@@ -1124,6 +1148,7 @@ class Application extends BaseApplication {
rootMenus.file,
rootMenus.edit,
rootMenus.view,
rootMenus.note,
rootMenus.tools,
rootMenus.help,
];

View File

@@ -18,6 +18,10 @@ class EventManager {
return this.emitter_.removeListener(eventName, callback);
}
off(eventName, callback) {
return this.removeListener(eventName, callback);
}
}
const eventManager = new EventManager();

View File

@@ -134,6 +134,8 @@ class HeaderComponent extends React.Component {
}
makeButton(key, style, options) {
const theme = themeStyle(this.props.theme);
let icon = null;
if (options.iconName) {
const iconStyle = {
@@ -158,6 +160,20 @@ class HeaderComponent extends React.Component {
const title = options.title ? options.title : '';
if (options.type === 'checkbox' && options.checked) {
finalStyle.backgroundColor = theme.selectedColor;
finalStyle.borderWidth = 1;
finalStyle.borderTopColor = theme.selectedDividerColor;
finalStyle.borderLeftColor = theme.selectedDividerColor;
finalStyle.borderTopStyle = 'solid';
finalStyle.borderLeftStyle = 'solid';
finalStyle.paddingLeft++;
finalStyle.paddingTop++;
finalStyle.paddingBottom--;
finalStyle.paddingRight--;
finalStyle.boxSizing = 'border-box';
}
return (
<a
className={classes.join(' ')}
@@ -256,6 +272,8 @@ class HeaderComponent extends React.Component {
height: theme.headerHeight,
display: 'flex',
alignItems: 'center',
paddingTop: 1,
paddingBottom: 1,
paddingLeft: theme.headerButtonHPadding,
paddingRight: theme.headerButtonHPadding,
color: theme.color,

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