1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-08-27 20:29:45 +02:00

Compare commits

...

190 Commits

Author SHA1 Message Date
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
531e92e2da Android release v1.0.327 2020-04-10 19:20:26 +01:00
Laurent Cozic
53a857b706 Electron release v1.0.199 2020-04-10 19:16:48 +01:00
Laurent Cozic
4f5473f8a2 All: Display WYSIWYG info in config screen only in desktop app 2020-04-10 19:16:18 +01:00
Laurent Cozic
1b617a4adc Android release v1.0.326 2020-04-10 18:57:20 +01:00
Laurent Cozic
7741a43d42 Electron release v1.0.198 2020-04-10 18:53:04 +01:00
Laurent Cozic
776411f882 Desktop: WYSIWYG: Added support for tables 2020-04-10 18:22:17 +00:00
Laurent Cozic
ae4cecc621 Desktop: WYSIWYG: Handle internal note links 2020-04-10 17:59:51 +00:00
Laurent Cozic
5be98a46e3 Desktop: WYSIWYG: Improved documentation on what plugins are compatible with WYSIWYG editor 2020-04-10 17:33:09 +00:00
Laurent Cozic
1907ef0c99 Tools: Dynamically generate PortableApps images 2020-04-10 18:19:17 +01:00
Laurent Cozic
bd99b25848 Desktop: WYSIWYG: Preserve HTML code in Markdown when editing from wysiwyg editor 2020-04-10 17:12:41 +00:00
Laurent Cozic
12a7b3c73c Desktop: WYSIWYG: Do not display target list in link dialog 2020-04-09 19:05:07 +01:00
Laurent Cozic
75b28c46af Desktop: Wait for note to be saved before closing the app 2020-04-09 18:57:20 +01:00
Laurent Cozic
93dccf62df Desktop: WYSIWYG: Added support for custom icon package and set Attach toolbar icon 2020-04-09 17:49:56 +01:00
Laurent Cozic
ec499eecd5 Desktop: WYSIWYG: Added support for code blocks edition and creation 2020-04-09 17:47:12 +01:00
Laurent Cozic
7ccd19e21d Cli: Fixes #2981: Add support for retrying decryption after it has failed multiple times 2020-04-08 18:02:31 +01:00
Laurent Cozic
a6459d3641 Mobile: Fixes #2941: Fixed resource download auto mode 2020-04-08 17:40:32 +00:00
Laurent Cozic
9b26378fdd All: Fixes #2709: When modifying a conflicted note, it would disappear from the view 2020-04-07 23:06:29 +00:00
Laurent Cozic
6b8e84332d Desktop: Resolves #2639: Resource screen improvements (#2850)
Co-authored-by: Georg Grab <talkdirty@users.noreply.github.com>
2020-04-08 11:12:25 +01:00
Laurent Cozic
6d56bb8afd Revert "Mobile: Fixes #2847: Fixed text issue in config screen on some devices (#2851)"
Reverted to fix regression (crash) in iOS app.

https://github.com/laurent22/joplin/pull/2851#issuecomment-610688103

This reverts commit e024015d5e.
2020-04-08 01:38:39 +01:00
Laurent Cozic
be9e873277 Mobile: Fixes #1437: Allow scrolling past end of note 2020-04-08 01:22:18 +01:00
Laurent Cozic
10feeeeb6b All: Fixes #2257: Prevent decryption loop when a resource cannot be decrypted 2020-04-08 01:00:01 +01:00
Laurent Cozic
518af9dc0a Desktop: Resolves #2681: Clear provisional flag as soon as note is modified to avoid data loss 2020-04-06 21:10:44 +00:00
Laurent Cozic
093fc811eb All: Fixes #2604: Fixed iOS and macOS icons and created script to automate icon creation 2020-04-06 20:55:40 +00:00
Tobias Johansson
2a8b27033f Desktop: Added new Aritim-Dark theme (#2997)
* Working on an aritim-dark theme, very WIP still

* Update aritim.js

* Added new theme "Aritim-Dark"

* removing aritim-dark "brace" theme for now

* Updating style with some changes

* Reset branch back to the original upstream state, to create a different pull-request for moving all themes outside

* Reset branch back to the original upstream state

* Moved all themes out into their own .js theme files

* Updating master to use the seperated themes stuff

* Moved themes into their own const

* Removed switch case for themes and simplified it. Thanks Laurent for the help!

* Adding Aritim-Dark to theme options
2020-04-07 10:23:40 +01:00
Tobias Johansson
62bddd7c69 Desktop: Moved all themes out into their own .js theme files (#2951)
* Working on an aritim-dark theme, very WIP still

* Update aritim.js

* Added new theme "Aritim-Dark"

* removing aritim-dark "brace" theme for now

* Updating style with some changes

* Reset branch back to the original upstream state, to create a different pull-request for moving all themes outside

* Reset branch back to the original upstream state

* Moved all themes out into their own .js theme files

* Updating master to use the seperated themes stuff

* Moved themes into their own const

* Removed switch case for themes and simplified it. Thanks Laurent for the help!
2020-04-07 09:19:53 +01:00
Helmut K. C. Tessarek
113f91b3c5 Update website 2020-04-05 20:39:10 -04:00
Helmut K. C. Tessarek
d40ad2de89 Update translations 2020-04-05 20:37:15 -04:00
Kwangmyung Kim
3b7733018c All: Translation: Update ko.po (#2986)
* Translation: Update ko.po

* Update README.md

Both the words ‘한국어’ and ‘한국말’ are composed of two meaning blocks : Korean + language.

어, which can be written in the Chinese character 語, means “language”, and 말, which is a native Korean word, also means “language”. So basically 한국어 and 한국말 are the same meaning, but I’d say 한국어 is a little more formal word while 한국말 is a more colloquial word.

‘어’ can be found in many other language names, but ‘말’ is not so commonly used other than in the word ‘한국말’ or ‘우리말(uri mal - ‘our language’, which is a word that Korean people use to mean ‘Korean’).

Here are some examples:

English = 영어
Chinese = 중국어
Japanese = 일본어
Arabic = 아랍어
Spanish = 스페인어
French =  프랑스어
Latin = 라틴어
Russian = 러시아어

* change incorect word

* Revert "Update README.md"

This reverts commit 88199a40df.
2020-04-05 20:31:15 -04:00
Helmut K. C. Tessarek
e319864669 Tools: Fix Korean language name: 한국어 (#2989) 2020-04-05 21:14:31 +01:00
Ishant Gupta
7251813634 Mobile: Fixed config text input not visible in dark mode (#2910) 2020-04-05 20:47:30 +01:00
Ishant Gupta
e024015d5e Mobile: Fixes #2847: Fixed text issue in config screen on some devices (#2851)
* fixed text issue on some devices and closes #2847

* changes added to global file
2020-04-05 20:05:04 +01:00
Gen Neko
3f5e6d72d6 Fix a blank Goto Anything screen (#2983) 2020-04-05 08:55:00 +01:00
abonte
9fd43a30f9 All: Translation: Update it_IT.po (#2978) 2020-04-04 21:08:33 -04:00
Laurent Cozic
0707a0e05f Merge branch 'master' of github.com:laurent22/joplin 2020-04-04 18:31:56 +01:00
Laurent Cozic
f550d847c4 Desktop: Resolves #2688: Add "Retry All" option to synchronisation page
Based on pull request #2712 by fhfuih

commit 7d3815e51dbf682ecc1ed3544a15e51b79d5d12d
Author: Laurent Cozic <laurent@cozic.net>
Date:   Sat Apr 4 18:26:01 2020 +0100

    Fix issues

commit dad1d2c4d4eb37fdd23096a3344cf4f14498e335
Merge: 16cf0a3058 0a1ba511ea
Author: Laurent Cozic <laurent@cozic.net>
Date:   Sat Apr 4 18:09:46 2020 +0100

    Merge branch 'master' of https://github.com/fhfuih/joplin into fhfuih-master

commit 0a1ba511ea
Merge: 6207b42044 bdd760f343
Author: fhfuih <fhfuih@outlook.com>
Date:   Sat Mar 14 20:30:27 2020 -0400

    Merge branch 'master' into master

commit 6207b42044
Author: fhfuih <sam.zyhuang@outlook.com>
Date:   Sat Mar 14 20:13:28 2020 -0400

    Decryption Retry All RN frontend

commit 6f46d1985f
Author: fhfuih <sam.zyhuang@outlook.com>
Date:   Sat Mar 14 20:10:55 2020 -0400

    Decryption Retry All backend & desktop

commit 4c3d37b311
Author: fhfuih <sam.zyhuang@outlook.com>
Date:   Fri Mar 13 13:21:21 2020 -0400

    Revert "Add "retry all" option to synchronisation page"

    This reverts commit 15daaa16fd.

commit cffc919791
Author: fhfuih <sam.zyhuang@outlook.com>
Date:   Fri Mar 13 13:19:51 2020 -0400

    Revert "Refactor "retry all" renderer to avoid hacks"

    This reverts commit 51ba6ea655.

commit 51ba6ea655
Author: fhfuih <sam.zyhuang@outlook.com>
Date:   Tue Mar 10 18:27:22 2020 -0400

    Refactor "retry all" renderer to avoid hacks

commit 57d71712e1
Merge: 15daaa16fd b367955e56
Author: fhfuih <sam.zyhuang@outlook.com>
Date:   Mon Mar 9 20:42:02 2020 -0400

    Merge branch 'master' of https://github.com/fhfuih/joplin

commit 15daaa16fd
Author: fhfuih <sam.zyhuang@outlook.com>
Date:   Mon Mar 9 20:36:09 2020 -0400

    Add "retry all" option to synchronisation page
2020-04-04 18:30:13 +01:00
Siddhant Sehgal
1e9a0036e7 Desktop: Resolves #2296: Right-click to move note to notebook (#2566)
* Assign Notebook functionality added

* Made the changes acc. to laurent22

* Changes done till 9/03/20

* Removed NoteContentPropertiesDialog.js

* Render notebooks in tree hierarchy

* Update PromptDialog.jsx

Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2020-04-04 18:03:09 +01:00
Laurent Cozic
16cf0a3058 Merge branch 'master' of github.com:laurent22/joplin 2020-04-04 17:36:29 +01:00
Helmut K. C. Tessarek
0fa19a281c fix heading 2020-04-04 03:46:23 -04:00
Mats Estensen
9c53f485e6 All: Translation: Update nb_NO.po (#2973) 2020-04-03 18:21:20 -04:00
Li
dc131f77d4 All: Translation: Update zh_CN.po (#2971) 2020-04-03 18:20:17 -04:00
Laurent Cozic
712601b8c0 Desktop: WYSIWYG: Apply theme to toolbar 2020-04-03 18:12:14 +00:00
Caleb John
ff7775b344 Desktop: Add friendly default filenames to export options (#2749)
* Add friendly default filenames to export options

* remove extension from safefilename call

* Load parent folder for all exports

* convert foldername and filename to friendly versions separatly

* Add null guards to the filename export
2020-04-03 00:24:33 +01:00
Laurent Cozic
0512fa6208 Desktop: Fixed regression when drag and dropping files 2020-04-02 23:01:14 +01:00
Laurent Cozic
27ab2b8e30 Desktop: Fixed styling issues with rendered Markdown 2020-04-02 22:54:06 +01:00
Laurent Cozic
9fa7c9c20a Desktop: WYSIWYG: Better handling of links on macOS and inside Katex block 2020-04-02 19:58:25 +01:00
Laurent Cozic
6bd0250ef8 Desktop: WYSIWYG: Handle resource download mode 2020-04-02 18:16:11 +01:00
Laurent Cozic
0d736bcb58 CLI v1.0.162 2020-04-01 18:18:41 +01:00
Laurent Cozic
c1129604ba Tools: Disable logging to console by default 2020-04-01 18:15:40 +01:00
Laurent Cozic
693f6cbfe7 Desktop: Add message box to ask confirmation when resizing image 2020-03-31 22:40:38 +01:00
Jonas
7f397a4da8 Fix Joplin_install_and_update.sh (#2913)
Make space after '"tag_name":' optional, to avoid the whole script failing without further notice.

Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2020-03-30 18:36:10 +01:00
Siddhant Sehgal
52ba5c5bb7 Desktop: Close Goto Anything modal on outside click (#2940)
* Close goto modal on outside click

* Renamed for consistency

Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2020-03-30 18:33:36 +01:00
Laurent Cozic
ce24942a03 Update website 2020-03-30 18:23:46 +01:00
Laurent Cozic
719afba05a Electron release v1.0.197 2020-03-30 18:03:36 +01:00
Laurent Cozic
c99dd2ba87 Tools: Fixed Electron build 2020-03-30 18:03:02 +01:00
Helmut K. C. Tessarek
a59cc94afc Update translations 2020-03-30 11:03:18 -04:00
nmiquan
336cadbc12 Translation: add Vietnamese (vi.po) (#2931)
* Vietnamese Translation

* removed cli messages

* updated translation
2020-03-30 10:59:49 -04:00
Laurent Cozic
a946dc69c1 Android release v1.0.325 2020-03-29 20:12:36 +01:00
Laurent Cozic
e2e95df057 Electron release v1.0.196 2020-03-29 20:06:44 +01:00
Laurent Cozic
e095369e1a Desktop: WYSIWYG: Prevent scripts from loading multiple times 2020-03-29 20:06:13 +01:00
Laurent Cozic
1c938a5998 Update build_troubleshooting.md 2020-03-29 11:08:53 +01:00
Anjula Karunarathne
a45128807e Desktop: Resolves #2683: Go To Anything by body (#2686)
* Go to anything by body

* Made limit parameter required

* Made parameter required

Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2020-03-28 13:05:00 +00:00
Brandon Wulf
d54e52b1a8 Desktop: Cleaning up the install / update script (#2719)
* Cleaning up the install script

* New `--force` option always downloads the latest version
* New `--notes` option shows the release notes
* Actually print the version installed at the end
* Show download progress, but not extra garbage
* Blue logo

* refactor and cleanup

* Handle space, but no args
2020-03-27 21:58:04 -04:00
Laurent Cozic
5743729d11 Desktop: WYSIWYG: Getting links to work 2020-03-27 18:28:13 +00:00
Laurent Cozic
11d8466db1 Desktop: WYSIWYG: Getting links to work 2020-03-27 18:26:52 +00:00
Laurent Cozic
c3360d6c48 Merge branch 'master' of github.com:laurent22/joplin 2020-03-27 16:12:43 +00:00
Bart
37158fdb89 Desktop: Fixes #2334: MD importer handle special chars in linked image name (#2346)
* md importer: decode uri encoded links to cover case of special chars in linked image names

* md importer: temp debug logs for linux test pipeline

* md importer: more temp debug logs

* md importer: tests, add special char image name on linux

* md importer: tests, use const not let

* md importer: remove debug logs
2020-03-27 12:20:38 +00:00
anirudh murali
ae73051797 Desktop: Fixes #2917: Prevent title overflow on small windows (#2918) 2020-03-27 12:16:40 +00:00
Ishant Gupta
62db3d09ea Mobile: Fixes #2900: Fixed bold text not visible in dark theme (#2909)
* fixed bold text not visible in oled dark theme

* fixed variable names
2020-03-27 12:13:22 +00:00
Amit singh
f82aa0adde Mobile: Fixes #2867: Fixed styling issue in config screen (#2868)
* fixes hidden path text

* fixes hidden path text lable
2020-03-27 12:09:26 +00:00
Laurent Cozic
5e5b6cdc42 Desktop: WYSIWYG: Restored focus logic and fixed undo issue 2020-03-26 17:19:13 +00:00
Gen NK
351306eb03 Translation: Update ja_JP.po (#2914) 2020-03-26 11:55:00 -04:00
Laurent Cozic
e4fffa52d4 Doc: Updated info about unit tests and feature tests 2020-03-25 17:56:05 +00:00
Ishant Gupta
d622ff4a78 Mobile: Fixes #2843: Fixed overlapping text in Dropbox sync page (#2845) 2020-03-25 11:18:39 +00:00
Devon Zuegel
b6d4fd16c9 Mobile: Add toolbar, list continuation and Markdown preview to editor (#2224)
* The basic editor is working! No list continuation still though

* List continuation is working! Now to delete when entering again and not typing on line + handle ordered lists

* Supports checkboxes + attempted at setting font

* Editor font works now; now need to fix the delete (look at past state)

* Fix deletion problem

* Add ordered list handler

* Add comments

* Extract insertListLine

* End lists on enter for empty bullets

* Add MarkdownView (renders badly though)

* Save edited text from MarkdownEditor

* Cleanup

* Refactor react-native-markdown-editor/

* Rename react-native-markdown-editor/ => MarkdownEditor/

* Cleanup

* Fix preview styles; still need to fix checkbox problem

* Fix keyboard padding

* Change name back to #body_changeText

* Incorporate PR feedback from @laurent22

* wip: Move MarkdownEditor/ from ReactNativeClient/lib/ to ReactNativeClient/

* Move MarkdownEditor/ from ReactNativeClient/lib/ to ReactNativeClient/

* Remove log statement

* Focus TextInput in MarkdownEditor from grandparent

* Make eslint happy

* Extract textInputRefName to shared variable

* Remove accidental #setState

* Cleanup

* Cleanup

* Run linter

* Cleanup

* Update button order

* Improve styles for config descriptions

* Allow descriptions to be added to BOOL type Setting configs

* Add editorBeta Setting

* Move FailSafe details to description text

* Update descriptionText styles

* Put the editor under the beta flag toggle

* Incorporate PR feedback from @laurent22

* Refactor Markdown editor focusing

* Cleanup

* Reorder MarkdownEditor formats

* Make applyListFormat behavior more intuitive

* Add comment

* Show MarkdownEditor with preview by default

* Show preview by default, then hide on typing

* Fix MarkdownEditor selection bug

* Cleanup

* Update Markdown button styles

* Make Markdown button colors theme-conscious

* Fix merge conflict resolution mistake

* Fix broken import

* Delete package-lock.json

* Reset package-lock.json

Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2020-03-25 10:50:45 +00:00
Sergey
a548d695f2 Doc: Update macOS troubleshooting for IOS build (#2898) 2020-03-25 09:31:05 +00:00
Aaron Murray
499f318192 Doc: Update text to match Desktop text (#2897) 2020-03-24 19:01:45 +00:00
Laurent Cozic
42ecf98344 Doc: Added build troubleshooting document 2020-03-24 17:22:44 +00:00
Caleb John
6f08e1e4ff Linux: Quit out of install script early if an ARM or 32 bit system is detected (#2881)
* Quit out of install script early if an arm system is detected

* improved error message
2020-03-24 17:08:07 +00:00
Helmut K. C. Tessarek
9524eb7e37 Update translations 2020-03-23 20:57:14 -04:00
Helmut K. C. Tessarek
84e478f8fe update de_DE.po 2020-03-23 20:56:09 -04:00
Arda Kılıçdağı
0cc77f99ac Translation: Update tr_TR.po (#2880)
* Turkish translations updated

* fullpaths updated

* Language string now respects the original one

* Line endings fixed

* Mysterious new lines cleaned

* Revert Readme

* Old translations added back
2020-03-23 20:46:59 -04:00
Ishant Gupta
622049dfad Mobile: Fixes #2859: Fixed code block styling in OLED theme (#2876) 2020-03-23 23:15:00 +00:00
Siddhant Sehgal
65bcc58261 Desktop: Fixes #2824: Fixed top margin of toolbar (#2828)
* Fixed_topmargin

* Fixed todolist hover

* Revert "Fixed todolist hover"

This reverts commit 38a4da622e.
2020-03-23 22:47:10 +00:00
Ishant Gupta
9749a2b9b7 Mobile: Resolves #2738: Auto-save after closing a note (#2782)
* latest changes merged

* fixed linting syntax
2020-03-23 22:34:13 +00:00
krzysiekwie
7b365194ba Translation: Update pl_PL.po (#2869) 2020-03-23 14:33:04 -04:00
Laurent Cozic
6e5c2730f1 Android release v1.0.324 2020-03-23 09:44:22 +00:00
Laurent Cozic
8d045f0c96 Revert "Mobile: Resolves #2286: Always show scrollbar in editor mode (#2594)"
This reverts commit 91d864bded.

To try to fix this: https://github.com/laurent22/joplin/issues/2856
2020-03-23 09:35:54 +00:00
Laurent Cozic
c370358bd1 Merge branch 'master' of github.com:laurent22/joplin 2020-03-23 01:33:12 +00:00
Laurent Cozic
03e8d921d3 Desktop: Fixed a few WISIWYG issues with checkboxes 2020-03-23 01:32:28 +00:00
Laurent Cozic
6cf624c18d Delete linkToLocal.sh 2020-03-23 00:57:33 +00:00
Laurent Cozic
41acdce165 Desktop: Added support for checkboxes and fixed various issues with WYSIWYG editor 2020-03-23 00:47:25 +00:00
Guilherme Teixeira
c607444c23 Translation: Update pt_BR.po (#2846) 2020-03-22 20:09:14 -04:00
Sergey
940198b9a0 Translation: Update ru_RU.po (#2839) 2020-03-22 20:08:03 -04:00
Laurent Cozic
9c3cf705c6 Update website 2020-03-22 22:24:37 +00:00
Laurent Cozic
6027725fae ios-v10.0.46 2020-03-22 20:07:49 +00:00
Laurent Cozic
054aa52bc8 Android release v1.0.323 2020-03-22 19:50:02 +00:00
Laurent Cozic
f4a562bc3c Electron release v1.0.195 2020-03-22 19:43:12 +00:00
Laurent Cozic
de8bf33ad9 Tools: Disabling tool checks for now as it is printing many unecessary messages to log 2020-03-22 19:41:23 +00:00
妙呀
159eaf7899 Translation: Update zh_CN.po (#2825)
Adjusted and added some Chinese simplified translation text
I like Joplin very much and hope to do my best
------------
调整和新增了部分中文简体翻译文本
非常喜欢Joplin,希望能尽绵薄之力
2020-03-21 14:34:31 -04:00
lighthousebulb
1bc045eb18 Translation: Update de_DE.po (#2806)
updated some translation strings
2020-03-21 14:33:40 -04:00
Laurent Cozic
0f934e48bc Revert "Desktop: Fixes #2467: Menu items were not disabled in viewer-only mode (#2576)"
Some items still need to be enabled so implementation needs to be
reviewed. Reverting for now.

This reverts commit 0be982c798.
2020-03-21 13:18:37 +00:00
Naveen M V
3ecd29d0b5 All: Fixes #2803: Remember last selected note (#2809)
* Fix : Remember last selected note

Fixes #2803

* Add unit test
2020-03-21 11:29:22 +00:00
Helmut K. C. Tessarek
42498842b5 translation fixes 2020-03-19 00:02:36 -04:00
Helmut K. C. Tessarek
ed4fbf093d Update translations 2020-03-18 23:49:06 -04:00
StarFang208
96b19027ec Translation: Update it_IT.po (#2791)
* Update it_IT.po

New translated lines and translator's name fixed.

* fix it_IT.po

Added the removed lines.
2020-03-18 23:47:48 -04:00
nullpointer666
cdb8a1e98c Desktop: fix menu bar shortcut (Alt+E) while editing a note (Win) (#2776) 2020-03-17 12:49:36 -04:00
mic704b
8cd26c9380 All: Fix integration test harness issues. (#2723) 2020-03-16 13:30:54 +11:00
Helmut K. C. Tessarek
0863f0d564 Desktop: Add global shortcut to show/hide Joplin (#2653)
Previously it was possible to open Joplin by clicking on the tray icon.
This functionality is no longer available due to changes in Electron.

The following key combination will show or hide Joplin in the current workspace:

- macOS: `Cmd+Opt+J`
- Linux, Windows: `Ctrl+Alt+J`
2020-03-16 00:36:18 +00:00
mic704b
63a2f7b7a5 All: Fix calls to non-existent function (#2675)
* Fix calls to non-existent function

* Add tests.

* Fix travis lint problems.
2020-03-16 08:53:49 +11:00
PackElend
9b562276f3 updated ## Recommended steps
- replaced Development by Features in `4. Come up with project that you're interested in and discuss it in [Features category](https://discourse.joplinapp.org/c/features)`
-
2020-03-15 21:52:24 +01:00
mic704b
a17e01793e All: Fix format of note timestamps (#2672)
* Correct format of timestamps during unserialization.

* Add tests.
2020-03-15 12:07:01 +00:00
mic704b
7fb061ea76 Desktop: Fix show-all-notes update bugs. (#2642) 2020-03-15 12:05:17 +00:00
gasolin
91d864bded Mobile: Resolves #2286: Always show scrollbar in editor mode (#2594)
* Mobile: show scrollbar in editor mode

* always show the scrollbar
2020-03-15 11:55:41 +00:00
Laurent Cozic
339d7d16c7 Desktop: Fix issue with tag not being displayed below note when it is created 2020-03-15 11:45:33 +00:00
Laurent Cozic
8e5762c3a3 Merge branch 'master' of github.com:laurent22/joplin 2020-03-15 10:19:46 +00:00
Rahul Mohata
50a811720f Mobile: Fixes #2695: Fixed colour of background in OLED dark theme (#2757) 2020-03-15 10:13:44 +00:00
Anjula Karunarathne
c2e1c4c1e1 Desktop: Fixes #2700: Fix button label wrapping in toolbar (#2708) 2020-03-15 09:49:24 +00:00
Laurent Cozic
e7b11a2d82 Desktop: Fixes #2618: Fixed renaming tag issue 2020-03-15 09:47:47 +00:00
Laurent Cozic
61d3582357 Restored note history feature by Naveen M V <naveenmv7@gmail.com> 2020-03-15 09:40:57 +00:00
Laurent Cozic
9e66219690 Revert "Desktop: Reolves #2409: Added arrows to go front and back through note history (#2563)"
Fixing merge issue...

This reverts commit d049b8846c.
2020-03-15 09:40:12 +00:00
Naveen M V
d049b8846c Desktop: Reolves #2409: Added arrows to go front and back through note history (#2563) 2020-03-15 09:38:19 +00:00
RedDocMD
bdd760f343 Desktop: Resolves #2703: Fast notebook deletion (#2730)
* Batch delete for Notebooks added

* Extended the Folder.delete() unit test

* Unit tests added for Note.batchDelete()
2020-03-14 21:01:45 +11:00
Helmut K. C. Tessarek
1ee88618e8 reencrypt -> re-encrypt 2020-03-13 20:52:28 -04:00
Laurent Cozic
e399474b4e Tools: Apply "curly" eslint rule 2020-03-13 23:57:34 +00:00
Laurent Cozic
e2e55b6e08 Tools: Apply eslint space-infix-ops rule 2020-03-13 23:52:40 +00:00
Laurent Cozic
d0d2bad7f4 Tools: Enforce and apply eslint rules prefer-const and no-var 2020-03-13 23:46:14 +00:00
Laurent Cozic
92bee549a1 Electron release v1.0.194 2020-03-13 23:24:56 +00:00
Ishant Gupta
02121f66de Mobile: Resolves #2721: Added button to select all notes (#2744)
* added feature of select all and closes #2721

* added functionality of toggle select all button to deselect all notes
2020-03-13 22:41:56 +00:00
Ishant Gupta
115eb6f511 Mobile: Resolves #2715: Close sidebar when displaying error dialog (#2720)
* added a new close function to close sidebar for an error and fixed #2715

* changed function to inline call of dispatch fixes #2715
2020-03-13 19:58:17 +00:00
Rahul Mohata
d208da577f iOS: Resolves #2701: Removed "Export debug report" button (#2704) 2020-03-13 19:55:26 +00:00
Runo Saduwa
62665899c6 Tools: Added build tools detection script (#2661)
* finished writing first script version

* added preinstall script

* removed white space and platform if statements

* remove error log

* removed install function

* corrected commands

* changed ls to list
2020-03-13 18:48:17 +00:00
Laurent Cozic
7640839f92 Let's leave that undocumented for now
Not the recommended way to create tags - there's the /tags and /notes/:id/tags for this
2020-03-13 18:46:38 +00:00
Yuvaraj J
cda837247a API: Resolves #941: Add support for "tags" property for note PUT request (#2649)
* API: Fix updation of tags when updating a note

* Add test for fix updation of tags

* Rewrite unit test and change variable name

* Use consistent variables and function calls

* Use default function call and change variable

* Handle case when tags is empty string

* Restructure test cases and improve tags check

* Update documentation
2020-03-13 18:44:47 +00:00
Laurent Cozic
8e2ba0d963 Android release v1.0.322 2020-03-13 17:56:56 +00:00
Laurent Cozic
f4958de885 All: Security: Added way to upgrade master key encryption and sync target encryption 2020-03-13 17:42:50 +00:00
Helmut K. C. Tessarek
3917e3469d Desktop: Add button to About box to copy Joplin's information to the clipboard (#2711)
* add button to About box to copy Joplin's information to the clipboard

On certain OS it is not possible to copy the text in the About window.
This change allows to copy that info to the Clipboard.

Due to some shortfalls in Electron, it is not possible to set `defaultId` and `cancelId` to 0.
(Actually one can set them to 0, but the result is not what one would expect.)
Thus I had to move the default `OK` button to the left.

I also added a hack to position the `OK` button approximately in the middle of the dialog box (if the copyLabel is not longer than 14 characters).

* remove hack to position button

* add a new bridge function showMessageBox

The function returns the index of the clicked button.

* we don't need the first 3 lines (product name and copyright)
2020-03-12 23:13:18 +00:00
624 changed files with 66675 additions and 34396 deletions

View File

@@ -21,38 +21,41 @@ Clipper/content_scripts/Readability.js
Clipper/dist
Clipper/icons
Clipper/popup/build
Clipper/popup/config/webpack.config.js
Clipper/popup/node_modules
Clipper/popup/scripts/build.js
docs/
ElectronClient/dist
ElectronClient/gui/editors/TinyMCE/plugins/lists.js
ElectronClient/lib
ElectronClient/lib/vendor/sjcl-rn.js
ElectronClient/lib/vendor/sjcl.js
ElectronClient/locales
ElectronClient/node_modules
ElectronClient/packageInfo.js
highlight.pack.js
Modules/TinyMCE/JoplinLists/
node_modules/
ReactNativeClient/android
ReactNativeClient/ios
ReactNativeClient/lib/joplin-renderer/assets/
ReactNativeClient/lib/joplin-renderer/vendor/fountain.min.js
ReactNativeClient/lib/rnInjectedJs/
ReactNativeClient/lib/vendor/
ReactNativeClient/lib/welcomeAssets.js
ReactNativeClient/locales
ReactNativeClient/node_modules
ReactNativeClient/pluginAssets/
readme/
Tools/node_modules
Tools/PortableAppsLauncher
Server/.git/
Server/.github/
Server/docs/
Server/dist/
Server/bin/
Server/dist/
Server/docs/
Server/node_modules/
ElectronClient/packageInfo.js
ReactNativeClient/pluginAssets/
ReactNativeClient/lib/joplin-renderer/vendor/fountain.min.js
ReactNativeClient/lib/joplin-renderer/assets/
ReactNativeClient/lib/rnInjectedJs/
Clipper/popup/config/webpack.config.js
Clipper/popup/scripts/build.js
Tools/node_modules
Tools/PortableAppsLauncher
Modules/TinyMCE/IconPack/postinstall.js
# AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD
ElectronClient/gui/editors/PlainEditor.js
@@ -64,6 +67,7 @@ ElectronClient/gui/ResourceScreen.js
ElectronClient/gui/ShareNoteDialog.js
ElectronClient/gui/utils/NoteText.js
ReactNativeClient/lib/AsyncActionQueue.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
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/sanitize_html.js

View File

@@ -54,16 +54,21 @@ module.exports = {
// This error is always a false positive so far since it detects
// possible race conditions in contexts where we know it cannot happen.
"require-atomic-updates": 0,
"prefer-const": ["error"],
"no-var": ["error"],
// Checks rules of Hooks
"react-hooks/rules-of-hooks": "error",
// Checks effect dependencies
"react-hooks/exhaustive-deps": "warn",
// Disable because of this: https://github.com/facebook/react/issues/16265
// "react-hooks/exhaustive-deps": "warn",
// -------------------------------
// Formatting
// -------------------------------
"space-in-parens": ["error", "never"],
"space-infix-ops": ["error"],
"curly": ["error", "multi-line", "consistent"],
"semi": ["error", "always"],
"eol-last": ["error", "always"],
"quotes": ["error", "single"],
@@ -92,7 +97,7 @@ module.exports = {
"multiline-comment-style": ["error", "separate-lines"],
"space-before-blocks": "error",
"spaced-comment": ["error", "always"],
"keyword-spacing": ["error", { "before": true, "after": true }]
"keyword-spacing": ["error", { "before": true, "after": true }],
},
"plugins": [
"react",

2
.github/stale.yml vendored
View File

@@ -1,6 +1,6 @@
# 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

1
.gitignore vendored
View File

@@ -59,6 +59,7 @@ ElectronClient/gui/ResourceScreen.js
ElectronClient/gui/ShareNoteDialog.js
ElectronClient/gui/utils/NoteText.js
ReactNativeClient/lib/AsyncActionQueue.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
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/sanitize_html.js

View File

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 820 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 64 KiB

View File

@@ -22,19 +22,6 @@ Then you can test the various applications:
cd ElectronClient
npm start
If you'd like to auto-reload the app on changes rather than having to quit and restart it manually each time, you can use [watchman-make](https://facebook.github.io/watchman/docs/watchman-make.html):
```sh
cd ElectronClient
watchman-make -p '**/*.js' '**/*.jsx' --run "npm start"
```
It still requires you to quit the application each time you want it to rebuild, but at least you don't have to re-run `"npm start"` each time. Here's what the workflow loop looks like in practice:
1. Edit and save files in your text editor.
2. Switch to the Electron app and <kbd>cmd</kbd>+<kbd>Q</kbd> to quit it.
3. `watchman` immediately restarts the app for you (whereas usually you'd have to switch back to the terminal, type `"npm start"`, and hit enter).
## Testing the Terminal application
cd CliClient
@@ -47,12 +34,12 @@ 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.
Normally the bundler should start automatically with the application. If it doesn't run `npm start`.
Normally the bundler should start automatically with the application. If it doesn't, run `npm start`.
## Building the clipper
@@ -80,37 +67,21 @@ You can specify additional parameters when running the desktop or CLI applicatio
Most of the application is written in JavaScript, however new classes and files should generally be written in [TypeScript](https://www.typescriptlang.org/). All TypeScript files are generated next to the .ts or .tsx file. So for example, if there's a file "lib/MyClass.ts", there will be a generated "lib/MyClass.js" next to it. It is implemented that way as it requires minimal changes to integrate TypeScript in the existing JavaScript code base.
# Troubleshooting desktop application
## Hot reload
## On Linux and macOS
If you'd like to auto-reload the desktop app on changes rather than having to quit and restart it manually each time, you can use [watchman-make](https://facebook.github.io/watchman/docs/watchman-make.html):
If there's an error `while loading shared libraries: libgconf-2.so.4: cannot open shared object file: No such file or directory`, run `sudo apt-get install libgconf-2-4`
```sh
cd ElectronClient
watchman-make -p '**/*.js' '**/*.jsx' --run "npm start"
```
If you get a node-gyp related error, you might need to manually install it: `npm install -g node-gyp`.
It still requires you to quit the application each time you want it to rebuild, but at least you don't have to re-run `"npm start"` each time. Here's what the workflow loop looks like in practice:
If you get the error `libtool: unrecognized option '-static'`, follow the instructions [in this post](https://stackoverflow.com/a/38552393/561309) to use the correct libtool version.
1. Edit and save files in your text editor.
2. Switch to the Electron app and <kbd>cmd</kbd>+<kbd>Q</kbd> to quit it.
3. `watchman` immediately restarts the app for you (whereas usually you'd have to switch back to the terminal, type `"npm start"`, and hit enter).
## On Windows
# Troubleshooting
If node-gyp does not work (MSBUILD: error MSB3428: Could not load the Visual C++ component "VCBuild.exe"), you might need to install `windows-build-tools` using `npm install --global windows-build-tools`.
If `yarn dist` fails, it may need administrative rights.
If you get an `error MSB8020: The build tools for v140 cannot be found.` try to run with a different toolset version, eg `npm install --toolset=v141` (See [here](https://github.com/mapbox/node-sqlite3/issues/1124) for more info).
## Other issues
> The application window doesn't open or is white
This is an indication that there's an early initialisation error. Try this:
- In ElectronAppWrapper, set `debugEarlyBugs` to `true`. This will force the window to show up and should open the console next to it, which should display any error.
- In more rare cases, an already open instance of Joplin can create strange low-level bugs that will display no error but will result in this white window. A non-dev instance of Joplin, or a dev instance that wasn't properly closed might cause this. So make sure you close everything and try again. Perhaps even other Electron apps running (Skype, Slack, etc.) could cause this?
- Also try to delete node_modules and rebuild.
- If all else fails, switch your computer off and on again, to make sure you start clean.
> How to work on the app from Windows?
**You should not use WSL at all** because this is a GUI app that lives outside of WSL, and the WSL layer can cause all kind of very hard to debug issues. It can also lock files in node_modules that cannot be unlocked when the app crashes. (You need to restart your computer.) Likewise, don't run the TypeScript watch command from WSL.
So everything should be done from a Windows Command prompt or Windows PowerShell running as Administrator. All build and start commands are designed to work cross-platform, including on Windows.
Please read for the [Build Troubleshooting Document](https://github.com/laurent22/joplin/blob/master/readme/build_troubleshooting.md) for various tips on how to get the build working.

View File

@@ -23,13 +23,11 @@ Avoid listing multiple requests in one topic. One topic per request makes it eas
Finally, when submitting a pull request, don't forget to [test your code](#unit-tests).
# Contribute to the project
## Contributing to Joplin's translation
# Contributing to Joplin's translation
Joplin is available in multiple languages thanks to the help of its users. You can help translate Joplin to your language or keep it up to date. Please read the documentation about [Localisation](https://joplinapp.org/#localisation).
## Contributing to Joplin's code
# Contributing to Joplin's code
If you want to start contributing to the project's code, please follow these guidelines before creating a pull request:
@@ -41,7 +39,7 @@ If you want to start contributing to the project's code, please follow these gui
Building the apps is relatively easy - please [see the build instructions](https://github.com/laurent22/joplin/blob/master/BUILD.md) for more details.
### Coding style
## Coding style
Coding style is enforced by a pre-commit hook that runs eslint. This hook is installed whenever running `npm install` on any of the application directory. If for some reason the pre-commit hook didn't get installed, you can manually install it by running `npm install` at the root of the repository.
@@ -49,44 +47,45 @@ For new React components, please use [React Hooks](https://reactjs.org/docs/hook
For changes made to the Desktop client that affect the user interface, refer to `ElectronClient/app/theme.js` for all styling information. The goal is to create a consistent user interface to allow for easy navigation of Joplin's various features and improve the overall user experience.
### Unit tests
## Automated tests
When submitting a pull request for a new feature or bug fix, please add unit tests for your code. Unit testing GUI changes is not always possible so it is not required, but any change in a file under /lib for example should be unit tested.
When submitting a pull request for a new feature or bug fixes, please add automated tests for your code whenever possible. Tests in Joplin are divided in **unit tests** and **feature tests**.
* **Unit tests** are used to tests models, services or utility classes - they are relatively low level. Unit tests should be prefixed with the type of class that is being tested - for example "models_Folder" or "services_SearchEngine".
* **Feature tests** on the other hand are to test higher level functionalities such as interactions with the GUI and how they affect the underlying model. Often these tests would dispatch Redux actions, and inspect how the application state has been changed. The feature tests should be prefixed with "feature_", for example "feature_TagList". There's a good explanation on what qualifies as a feature test in [this post](https://github.com/laurent22/joplin/pull/2819#issuecomment-603502230).
The tests are under CliClient/tests. To get them running, you first need to build the CLI app:
```sh
npm run tsc # Build the .ts and .tsx files
cd CliClient
npm install
cd CliClient
```
To run all the test units:
```sh
npm run test
npm test
```
To run just one particular file:
```sh
npm run test -- --filter=markdownUtils # Don't add the .js extension
npm test -- --filter=markdownUtils # Don't add the .js extension
```
To filter tests. For example, to run all the test units that contain "should handle conflict" in their description:
```sh
npm run test -- --filter="should handle conflict"
npm test -- --filter="should handle conflict"
```
If you get the error `Cannot find module '/joplin/CliClient/node_modules/sqlite3/lib/binding/node-v79-darwin-x64/node_sqlite3.node'`, you may need to run `npm rebuild`.
## About abandoned pull requests
It happens that a pull request is started but not finished and despite our attempts to contact the contributor, we dont hear from them again.
It happens that a pull request is started but not finished and despite our attempts to contact the contributor, we don't hear from them again.
In that case we will not merge the pull request, even if only small changes are missing. Our policy is simply to close the pull request. Why? Because an unfinished pull request essentially means giving us work and moving on. We would rather not encourage this behaviour.
Also, please note that since we have spent time reviewing the pull request and proposing solutions, we reserve the right to re-use that knowledge to create a new pull request, potentially based on your changes.
Wed much prefer that you complete the pull request though, so well be sure to ping you a few times before that!
We'd much prefer that you complete the pull request though, so we'll be sure to ping you a few times before that!

View File

@@ -134,7 +134,7 @@ class AppGui {
const item = folderList.currentItem;
if (item === '-') {
let newIndex = event.currentIndex + (event.previousIndex < event.currentIndex ? +1 : -1);
const newIndex = event.currentIndex + (event.previousIndex < event.currentIndex ? +1 : -1);
let nextItem = folderList.itemAt(newIndex);
if (!nextItem) nextItem = folderList.itemAt(event.previousIndex);
@@ -186,7 +186,7 @@ class AppGui {
borderRightWidth: 1,
};
noteList.on('currentItemChange', async () => {
let note = noteList.currentItem;
const note = noteList.currentItem;
this.store_.dispatch({
type: 'NOTE_SELECT',
id: note ? note.id : null,
@@ -338,7 +338,7 @@ class AppGui {
if (consoleWidget.isMaximized__ === doMaximize) return;
let constraints = {
const constraints = {
type: 'stretch',
factor: !doMaximize ? 1 : 4,
};
@@ -415,10 +415,10 @@ class AppGui {
async handleModelAction(action) {
this.logger().info('Action:', action);
let state = Object.assign({}, defaultState);
const state = Object.assign({}, defaultState);
state.notes = this.widget('noteList').items;
let newState = reducer(state, action);
const newState = reducer(state, action);
if (newState !== state) {
this.widget('noteList').items = newState.notes;
@@ -485,9 +485,9 @@ class AppGui {
// this.logger().debug('Got command: ' + cmd);
try {
let note = this.widget('noteList').currentItem;
let folder = this.widget('folderList').currentItem;
let args = splitCommandString(cmd);
const note = this.widget('noteList').currentItem;
const folder = this.widget('folderList').currentItem;
const args = splitCommandString(cmd);
for (let i = 0; i < args.length; i++) {
if (args[i] == '$n') {
@@ -548,7 +548,7 @@ class AppGui {
stdout(text) {
if (text === null || text === undefined) return;
let lines = text.split('\n');
const lines = text.split('\n');
for (let i = 0; i < lines.length; i++) {
const v = typeof lines[i] === 'object' ? JSON.stringify(lines[i]) : lines[i];
this.widget('console').addLine(v);
@@ -626,7 +626,7 @@ class AppGui {
if (link.type === 'item') {
const itemId = link.id;
let item = await BaseItem.loadItemById(itemId);
const item = await BaseItem.loadItemById(itemId);
if (!item) throw new Error(`No item with ID ${itemId}`); // Should be nearly impossible
if (item.type_ === BaseModel.TYPE_RESOURCE) {
@@ -750,7 +750,7 @@ class AppGui {
// -------------------------------------------------------------------------
const shortcutKey = this.currentShortcutKeys_.join('');
let keymapItem = this.keymapItemByKey(shortcutKey);
const keymapItem = this.keymapItemByKey(shortcutKey);
// If this command is an alias to another command, resolve to the actual command
@@ -766,7 +766,7 @@ class AppGui {
if (keymapItem.type === 'function') {
this.processFunctionCommand(keymapItem.command);
} else if (keymapItem.type === 'prompt') {
let promptOptions = {};
const promptOptions = {};
if ('cursorPosition' in keymapItem) promptOptions.cursorPosition = keymapItem.cursorPosition;
const commandString = await statusBar.prompt(keymapItem.command ? keymapItem.command : '', null, promptOptions);
this.addCommandToConsole(commandString);

View File

@@ -47,7 +47,7 @@ class Application extends BaseApplication {
}
async loadItem(type, pattern, options = null) {
let output = await this.loadItems(type, pattern, options);
const output = await this.loadItems(type, pattern, options);
if (output.length > 1) {
// output.sort((a, b) => { return a.user_updated_time < b.user_updated_time ? +1 : -1; });
@@ -144,7 +144,7 @@ class Application extends BaseApplication {
if (options.type === 'boolean') {
if (answer === null) return false; // Pressed ESCAPE
if (!answer) answer = options.answers[0];
let positiveIndex = options.booleanAnswerDefault == 'y' ? 0 : 1;
const positiveIndex = options.booleanAnswerDefault == 'y' ? 0 : 1;
return answer.toLowerCase() === options.answers[positiveIndex].toLowerCase();
} else {
return answer;
@@ -181,7 +181,7 @@ class Application extends BaseApplication {
const ext = fileExtension(path);
if (ext != 'js') return;
let CommandClass = require(`./${path}`);
const CommandClass = require(`./${path}`);
let cmd = new CommandClass();
if (!cmd.enabled()) return;
cmd = this.setupCommand(cmd);
@@ -192,8 +192,8 @@ class Application extends BaseApplication {
}
if (uiType !== null) {
let temp = [];
for (let n in this.commands_) {
const temp = [];
for (const n in this.commands_) {
if (!this.commands_.hasOwnProperty(n)) continue;
const c = this.commands_[n];
if (!c.supportsUi(uiType)) continue;
@@ -207,8 +207,8 @@ class Application extends BaseApplication {
async commandNames() {
const metadata = await this.commandMetadata();
let output = [];
for (let n in metadata) {
const output = [];
for (const n in metadata) {
if (!metadata.hasOwnProperty(n)) continue;
output.push(n);
}
@@ -227,7 +227,7 @@ class Application extends BaseApplication {
const commands = this.commands();
output = {};
for (let n in commands) {
for (const n in commands) {
if (!commands.hasOwnProperty(n)) continue;
const cmd = commands[n];
output[n] = cmd.metadata();
@@ -251,7 +251,7 @@ class Application extends BaseApplication {
CommandClass = require(`${__dirname}/command-${name}.js`);
} catch (error) {
if (error.message && error.message.indexOf('Cannot find module') >= 0) {
let e = new Error(_('No such command: %s', name));
const e = new Error(_('No such command: %s', name));
e.type = 'notFound';
throw e;
} else {
@@ -362,7 +362,7 @@ class Application extends BaseApplication {
}
const output = [];
for (let n in itemsByCommand) {
for (const n in itemsByCommand) {
if (!itemsByCommand.hasOwnProperty(n)) continue;
output.push(itemsByCommand[n]);
}

View File

@@ -1,20 +1,20 @@
var { app } = require('./app.js');
var Note = require('lib/models/Note.js');
var Folder = require('lib/models/Folder.js');
var Tag = require('lib/models/Tag.js');
var { cliUtils } = require('./cli-utils.js');
var yargParser = require('yargs-parser');
var fs = require('fs-extra');
const { app } = require('./app.js');
const Note = require('lib/models/Note.js');
const Folder = require('lib/models/Folder.js');
const Tag = require('lib/models/Tag.js');
const { cliUtils } = require('./cli-utils.js');
const yargParser = require('yargs-parser');
const fs = require('fs-extra');
async function handleAutocompletionPromise(line) {
// Auto-complete the command name
const names = await app().commandNames();
let words = getArguments(line);
const words = getArguments(line);
// If there is only one word and it is not already a command name then you
// should look for commands it could be
if (words.length == 1) {
if (names.indexOf(words[0]) === -1) {
let x = names.filter(n => n.indexOf(words[0]) === 0);
const x = names.filter(n => n.indexOf(words[0]) === 0);
if (x.length === 1) {
return `${x[0]} `;
}
@@ -36,8 +36,8 @@ async function handleAutocompletionPromise(line) {
}
// complete an option
let next = words.length > 1 ? words[words.length - 1] : '';
let l = [];
const next = words.length > 1 ? words[words.length - 1] : '';
const l = [];
if (next[0] === '-') {
for (let i = 0; i < metadata.options.length; i++) {
const options = metadata.options[i][0].split(' ');
@@ -60,7 +60,7 @@ async function handleAutocompletionPromise(line) {
if (l.length === 0) {
return line;
}
let ret = l.map(a => toCommandLine(a));
const ret = l.map(a => toCommandLine(a));
ret.prefix = `${toCommandLine(words.slice(0, -1))} `;
return ret;
}
@@ -69,7 +69,7 @@ async function handleAutocompletionPromise(line) {
// words that don't start with a - less one for the command name
const positionalArgs = words.filter(a => a.indexOf('-') !== 0).length - 1;
let cmdUsage = yargParser(metadata.usage)['_'];
const cmdUsage = yargParser(metadata.usage)['_'];
cmdUsage.splice(0, 1);
if (cmdUsage.length >= positionalArgs) {
@@ -95,29 +95,29 @@ async function handleAutocompletionPromise(line) {
}
if (argName == 'tag') {
let tags = await Tag.search({ titlePattern: `${next}*` });
const tags = await Tag.search({ titlePattern: `${next}*` });
l.push(...tags.map(n => n.title));
}
if (argName == 'file') {
let files = await fs.readdir('.');
const files = await fs.readdir('.');
l.push(...files);
}
if (argName == 'tag-command') {
let c = filterList(['add', 'remove', 'list', 'notetags'], next);
const c = filterList(['add', 'remove', 'list', 'notetags'], next);
l.push(...c);
}
if (argName == 'todo-command') {
let c = filterList(['toggle', 'clear'], next);
const c = filterList(['toggle', 'clear'], next);
l.push(...c);
}
}
if (l.length === 1) {
return toCommandLine([...words.slice(0, -1), l[0]]);
} else if (l.length > 1) {
let ret = l.map(a => toCommandLine(a));
const ret = l.map(a => toCommandLine(a));
ret.prefix = `${toCommandLine(words.slice(0, -1))} `;
return ret;
}
@@ -155,7 +155,7 @@ function getArguments(line) {
let inSingleQuotes = false;
let inDoubleQuotes = false;
let currentWord = '';
let parsed = [];
const parsed = [];
for (let i = 0; i < line.length; i++) {
if (line[i] === '"') {
if (inDoubleQuotes) {
@@ -192,7 +192,7 @@ function getArguments(line) {
return parsed;
}
function filterList(list, next) {
let output = [];
const output = [];
for (let i = 0; i < list.length; i++) {
if (list[i].indexOf(next) !== 0) continue;
output.push(list[i]);

View File

@@ -50,7 +50,7 @@ class BaseCommand {
async cancel() {}
name() {
let r = this.usage().split(' ');
const r = this.usage().split(' ');
return r[0];
}

View File

@@ -15,11 +15,11 @@ function wrap(text, indent) {
}
function renderOptions(options) {
let output = [];
const output = [];
const optionColWidth = getOptionColWidth(options);
for (let i = 0; i < options.length; i++) {
let option = options[i];
const option = options[i];
const flag = option[0];
const indent = INDENT + INDENT + ' '.repeat(optionColWidth + 2);
@@ -33,7 +33,7 @@ function renderOptions(options) {
}
function renderCommand(cmd) {
let output = [];
const output = [];
output.push(INDENT + cmd.usage());
output.push('');
output.push(wrap(cmd.description(), INDENT + INDENT));
@@ -48,14 +48,14 @@ function renderCommand(cmd) {
}
function getCommands() {
let output = [];
const output = [];
fs.readdirSync(__dirname).forEach(path => {
if (path.indexOf('command-') !== 0) return;
const ext = fileExtension(path);
if (ext != 'js') return;
let CommandClass = require(`./${path}`);
let cmd = new CommandClass();
const CommandClass = require(`./${path}`);
const cmd = new CommandClass();
if (!cmd.enabled()) return;
if (cmd.hidden()) return;
output.push(cmd);
@@ -73,7 +73,7 @@ function getOptionColWidth(options) {
}
function getHeader() {
let output = [];
const output = [];
output.push('NAME');
output.push('');
@@ -84,7 +84,7 @@ function getHeader() {
output.push('DESCRIPTION');
output.push('');
let description = [];
const description = [];
description.push('Joplin is a note taking and to-do application, which can handle a large number of notes organised into notebooks.');
description.push('The notes are searchable, can be copied, tagged and modified with your own text editor.');
description.push('\n\n');
@@ -98,7 +98,7 @@ function getHeader() {
}
function getFooter() {
let output = [];
const output = [];
output.push('WEBSITE');
output.push('');
@@ -120,10 +120,10 @@ async function main() {
// setLocale('fr_FR');
const commands = getCommands();
let commandBlocks = [];
const commandBlocks = [];
for (let i = 0; i < commands.length; i++) {
let cmd = commands[i];
const cmd = commands[i];
commandBlocks.push(renderCommand(cmd));
}

View File

@@ -40,8 +40,8 @@ function createClient(id) {
const client = createClient(1);
function execCommand(client, command) {
let exePath = `node ${joplinAppPath}`;
let cmd = `${exePath} --update-geolocation-disabled --env dev --profile ${client.profileDir} ${command}`;
const exePath = `node ${joplinAppPath}`;
const cmd = `${exePath} --update-geolocation-disabled --env dev --profile ${client.profileDir} ${command}`;
logger.info(`${client.id}: ${command}`);
return new Promise((resolve, reject) => {
@@ -129,8 +129,8 @@ testUnits.testCat = async () => {
await execCommand(client, 'mkbook nb1');
await execCommand(client, 'mknote mynote');
let folder = await Folder.loadByTitle('nb1');
let note = await Note.loadFolderNoteByField(folder.id, 'title', 'mynote');
const folder = await Folder.loadByTitle('nb1');
const note = await Note.loadFolderNoteByField(folder.id, 'title', 'mynote');
let r = await execCommand(client, 'cat mynote');
assertTrue(r.indexOf('mynote') >= 0);
@@ -149,7 +149,7 @@ testUnits.testConfig = async () => {
await Setting.load();
assertEquals('subl', Setting.value('editor'));
let r = await execCommand(client, 'config');
const r = await execCommand(client, 'config');
assertTrue(r.indexOf('editor') >= 0);
assertTrue(r.indexOf('subl') >= 0);
};
@@ -161,14 +161,14 @@ testUnits.testCp = async () => {
await execCommand(client, 'cp n1');
let f1 = await Folder.loadByTitle('nb1');
let f2 = await Folder.loadByTitle('nb2');
const f1 = await Folder.loadByTitle('nb1');
const f2 = await Folder.loadByTitle('nb2');
let notes = await Note.previews(f1.id);
assertEquals(2, notes.length);
await execCommand(client, 'cp n1 nb2');
let notesF1 = await Note.previews(f1.id);
const notesF1 = await Note.previews(f1.id);
assertEquals(2, notesF1.length);
notes = await Note.previews(f2.id);
assertEquals(1, notes.length);
@@ -179,7 +179,7 @@ testUnits.testLs = async () => {
await execCommand(client, 'mkbook nb1');
await execCommand(client, 'mknote note1');
await execCommand(client, 'mknote note2');
let r = await execCommand(client, 'ls');
const r = await execCommand(client, 'ls');
assertTrue(r.indexOf('note1') >= 0);
assertTrue(r.indexOf('note2') >= 0);
@@ -191,8 +191,8 @@ testUnits.testMv = async () => {
await execCommand(client, 'mknote n1');
await execCommand(client, 'mv n1 nb2');
let f1 = await Folder.loadByTitle('nb1');
let f2 = await Folder.loadByTitle('nb2');
const f1 = await Folder.loadByTitle('nb1');
const f2 = await Folder.loadByTitle('nb2');
let notes1 = await Note.previews(f1.id);
let notes2 = await Note.previews(f2.id);
@@ -218,18 +218,18 @@ async function main() {
logger.info(await execCommand(client, 'version'));
await db.open({ name: `${client.profileDir}/database.sqlite` });
BaseModel.db_ = db;
BaseModel.setDb(db);
await Setting.load();
let onlyThisTest = 'testMv';
onlyThisTest = '';
for (let n in testUnits) {
for (const n in testUnits) {
if (!testUnits.hasOwnProperty(n)) continue;
if (onlyThisTest && n != onlyThisTest) continue;
await clearDatabase();
let testName = n.substr(4).toLowerCase();
const testName = n.substr(4).toLowerCase();
process.stdout.write(`${testName}: `);
await testUnits[n]();
console.info('');

View File

@@ -11,27 +11,27 @@ cliUtils.printArray = function(logFunction, rows) {
const ALIGN_LEFT = 0;
const ALIGN_RIGHT = 1;
let colWidths = [];
let colAligns = [];
const colWidths = [];
const colAligns = [];
for (let i = 0; i < rows.length; i++) {
let row = rows[i];
const row = rows[i];
for (let j = 0; j < row.length; j++) {
let item = row[j];
let width = item ? item.toString().length : 0;
let align = typeof item == 'number' ? ALIGN_RIGHT : ALIGN_LEFT;
const item = row[j];
const width = item ? item.toString().length : 0;
const align = typeof item == 'number' ? ALIGN_RIGHT : ALIGN_LEFT;
if (!colWidths[j] || colWidths[j] < width) colWidths[j] = width;
if (colAligns.length <= j) colAligns[j] = align;
}
}
for (let row = 0; row < rows.length; row++) {
let line = [];
const line = [];
for (let col = 0; col < colWidths.length; col++) {
let item = rows[row][col];
let width = colWidths[col];
let dir = colAligns[col] == ALIGN_LEFT ? stringPadding.RIGHT : stringPadding.LEFT;
const item = rows[row][col];
const width = colWidths[col];
const dir = colAligns[col] == ALIGN_LEFT ? stringPadding.RIGHT : stringPadding.LEFT;
line.push(stringPadding(item, width, ' ', dir));
}
logFunction(line.join(' '));
@@ -39,7 +39,7 @@ cliUtils.printArray = function(logFunction, rows) {
};
cliUtils.parseFlags = function(flags) {
let output = {};
const output = {};
flags = flags.split(',');
for (let i = 0; i < flags.length; i++) {
let f = flags[i].trim();
@@ -76,11 +76,11 @@ cliUtils.parseCommandArg = function(arg) {
cliUtils.makeCommandArgs = function(cmd, argv) {
let cmdUsage = cmd.usage();
cmdUsage = yargParser(cmdUsage);
let output = {};
const output = {};
let options = cmd.options();
let booleanFlags = [];
let aliases = {};
const options = cmd.options();
const booleanFlags = [];
const aliases = {};
for (let i = 0; i < options.length; i++) {
if (options[i].length != 2) throw new Error(`Invalid options: ${options[i]}`);
let flags = options[i][0];
@@ -97,7 +97,7 @@ cliUtils.makeCommandArgs = function(cmd, argv) {
}
}
let args = yargParser(argv, {
const args = yargParser(argv, {
boolean: booleanFlags,
alias: aliases,
string: ['_'],
@@ -113,8 +113,8 @@ cliUtils.makeCommandArgs = function(cmd, argv) {
}
}
let argOptions = {};
for (let key in args) {
const argOptions = {};
for (const key in args) {
if (!args.hasOwnProperty(key)) continue;
if (key == '_') continue;
argOptions[key] = args[key];
@@ -134,7 +134,7 @@ cliUtils.promptMcq = function(message, answers) {
});
message += '\n\n';
for (let n in answers) {
for (const n in answers) {
if (!answers.hasOwnProperty(n)) continue;
message += `${_('%s: %s', n, answers[n])}\n`;
}

View File

@@ -56,7 +56,6 @@ class Command extends BaseCommand {
lines.push('# Joplin API');
lines.push('');
lines.push('When the Web Clipper service is enabled, Joplin exposes a [REST API](https://en.wikipedia.org/wiki/Representational_state_transfer) which allows third-party applications to access Joplin\'s data and to create, modify or delete notes, notebooks, resources or tags.');
lines.push('');
lines.push('In order to use it, you\'ll first need to find on which port the service is running. To do so, open the Web Clipper Options in Joplin and if the service is running it should tell you on which port. Normally it runs on port **41184**. If you want to find it programmatically, you may follow this kind of algorithm:');
lines.push('');

View File

@@ -14,9 +14,9 @@ class Command extends BaseCommand {
}
async action(args) {
let title = args['note'];
const title = args['note'];
let note = await app().loadItem(BaseModel.TYPE_NOTE, title, { parent: app().currentFolder() });
const note = await app().loadItem(BaseModel.TYPE_NOTE, title, { parent: app().currentFolder() });
this.encryptionCheck(note);
if (!note) throw new Error(_('Cannot find "%s".', title));

View File

@@ -18,9 +18,9 @@ class Command extends BaseCommand {
}
async action(args) {
let title = args['note'];
const title = args['note'];
let item = await app().loadItem(BaseModel.TYPE_NOTE, title, { parent: app().currentFolder() });
const item = await app().loadItem(BaseModel.TYPE_NOTE, title, { parent: app().currentFolder() });
if (!item) throw new Error(_('Cannot find "%s".', title));
const content = args.options.verbose ? await Note.serialize(item) : await Note.serializeForEdit(item);

View File

@@ -35,7 +35,7 @@ class Command extends BaseCommand {
});
inputStream.on('end', () => {
let json = chunks.join('');
const json = chunks.join('');
let settingsObj;
try {
settingsObj = JSON.parse(json);
@@ -83,7 +83,7 @@ class Command extends BaseCommand {
};
if (isExport || (!isImport && !args.value)) {
let keys = Setting.keys(!verbose, 'cli');
const keys = Setting.keys(!verbose, 'cli');
keys.sort();
if (isExport) {

View File

@@ -18,15 +18,15 @@ class Command extends BaseCommand {
async action() {
let items = [];
let folders = await Folder.all();
const folders = await Folder.all();
for (let i = 0; i < folders.length; i++) {
let folder = folders[i];
let notes = await Note.previews(folder.id);
const folder = folders[i];
const notes = await Note.previews(folder.id);
items.push(folder);
items = items.concat(notes);
}
let tags = await Tag.all();
const tags = await Tag.all();
for (let i = 0; i < tags.length; i++) {
tags[i].notes_ = await Tag.noteIds(tags[i].id);
}

View File

@@ -24,12 +24,11 @@ class Command extends BaseCommand {
['-p, --password <password>', 'Use this password as master password (For security reasons, it is not recommended to use this option).'],
['-v, --verbose', 'More verbose output for the `target-status` command'],
['-o, --output <directory>', 'Output directory'],
['--retry-failed-items', 'Applies to `decrypt` command - retries decrypting items that previously could not be decrypted.'],
];
}
async action(args) {
// change-password
const options = args.options;
const askForMasterKey = async error => {
@@ -44,6 +43,27 @@ class Command extends BaseCommand {
return true;
};
const startDecryption = async () => {
this.stdout(_('Starting decryption... Please wait as it may take several minutes depending on how much there is to decrypt.'));
while (true) {
try {
await DecryptionWorker.instance().start();
break;
} catch (error) {
if (error.code === 'masterKeyNotLoaded') {
const ok = await askForMasterKey(error);
if (!ok) return;
continue;
}
throw error;
}
}
this.stdout(_('Completed decryption.'));
};
if (args.command === 'enable') {
const password = options.password ? options.password.toString() : await this.prompt(_('Enter master password:'), { type: 'string', secure: true });
if (!password) {
@@ -73,24 +93,8 @@ class Command extends BaseCommand {
const plainText = await EncryptionService.instance().decryptString(args.path);
this.stdout(plainText);
} else {
this.stdout(_('Starting decryption... Please wait as it may take several minutes depending on how much there is to decrypt.'));
while (true) {
try {
await DecryptionWorker.instance().start();
break;
} catch (error) {
if (error.code === 'masterKeyNotLoaded') {
const ok = await askForMasterKey(error);
if (!ok) return;
continue;
}
throw error;
}
}
this.stdout(_('Completed decryption.'));
if (args.options['retry-failed-items']) await DecryptionWorker.instance().clearDisabledItems();
await startDecryption();
}
return;
@@ -138,7 +142,7 @@ class Command extends BaseCommand {
if (!targetPath) throw new Error('Please specify the sync target path.');
const dirPaths = function(targetPath) {
let paths = [];
const paths = [];
fs.readdirSync(targetPath).forEach(path => {
paths.push(path);
});
@@ -151,10 +155,10 @@ class Command extends BaseCommand {
let encryptedResourceCount = 0;
let otherItemCount = 0;
let encryptedPaths = [];
let decryptedPaths = [];
const encryptedPaths = [];
const decryptedPaths = [];
let paths = dirPaths(targetPath);
const paths = dirPaths(targetPath);
for (let i = 0; i < paths.length; i++) {
const path = paths[i];
@@ -164,7 +168,7 @@ class Command extends BaseCommand {
// this.stdout(fullPath);
if (path === '.resource') {
let resourcePaths = dirPaths(fullPath);
const resourcePaths = dirPaths(fullPath);
for (let j = 0; j < resourcePaths.length; j++) {
const resourcePath = resourcePaths[j];
resourceCount++;

View File

@@ -35,7 +35,7 @@ class Command extends BaseCommand {
// Load note or create it if it doesn't exist
// -------------------------------------------------------------------------
let title = args['note'];
const title = args['note'];
if (!app().currentFolder()) throw new Error(_('No active notebook.'));
let note = await app().loadItem(BaseModel.TYPE_NOTE, title);
@@ -91,7 +91,7 @@ class Command extends BaseCommand {
const updatedContent = await fs.readFile(tempFilePath, 'utf8');
if (updatedContent !== originalContent) {
let updatedNote = await Note.unserializeForEdit(updatedContent);
const updatedNote = await Note.unserializeForEdit(updatedContent);
updatedNote.id = note.id;
await Note.save(updatedNote);
this.stdout(_('Note has been saved.'));

View File

@@ -24,7 +24,7 @@ class Command extends BaseCommand {
}
async action(args) {
let exportOptions = {};
const exportOptions = {};
exportOptions.path = args.path;
exportOptions.format = args.options.format ? args.options.format : 'jex';

View File

@@ -14,9 +14,9 @@ class Command extends BaseCommand {
}
async action(args) {
let title = args['note'];
const title = args['note'];
let item = await app().loadItem(BaseModel.TYPE_NOTE, title, { parent: app().currentFolder() });
const item = await app().loadItem(BaseModel.TYPE_NOTE, title, { parent: app().currentFolder() });
if (!item) throw new Error(_('Cannot find "%s".', title));
const url = Note.geolocationUrl(item);
this.stdout(url);

View File

@@ -15,8 +15,8 @@ class Command extends BaseCommand {
allCommands() {
const commands = app().commands(app().uiType());
let output = [];
for (let n in commands) {
const output = [];
for (const n in commands) {
if (!commands.hasOwnProperty(n)) continue;
const command = commands[n];
if (command.hidden()) continue;
@@ -48,7 +48,7 @@ class Command extends BaseCommand {
.gui()
.keymap();
let rows = [];
const rows = [];
for (let i = 0; i < keymap.length; i++) {
const item = keymap[i];

View File

@@ -25,7 +25,7 @@ class Command extends BaseCommand {
}
async action(args) {
let folder = await app().loadItem(BaseModel.TYPE_FOLDER, args.notebook);
const folder = await app().loadItem(BaseModel.TYPE_FOLDER, args.notebook);
if (args.notebook && !folder) throw new Error(_('Cannot find "%s".', args.notebook));
@@ -39,7 +39,7 @@ class Command extends BaseCommand {
// onProgress/onError supported by Enex import only
importOptions.onProgress = progressState => {
let line = [];
const line = [];
line.push(_('Found: %d.', progressState.loaded));
line.push(_('Created: %d.', progressState.created));
if (progressState.updated) line.push(_('Updated: %d.', progressState.updated));
@@ -51,7 +51,7 @@ class Command extends BaseCommand {
};
importOptions.onError = error => {
let s = error.trace ? error.trace : error.toString();
const s = error.trace ? error.trace : error.toString();
this.stdout(s);
};

View File

@@ -34,11 +34,11 @@ class Command extends BaseCommand {
}
async action(args) {
let pattern = args['note-pattern'];
const pattern = args['note-pattern'];
let items = [];
let options = args.options;
const options = args.options;
let queryOptions = {};
const queryOptions = {};
if (options.limit) queryOptions.limit = options.limit;
if (options.sort) {
queryOptions.orderBy = options.sort;
@@ -70,19 +70,19 @@ class Command extends BaseCommand {
} else {
let hasTodos = false;
for (let i = 0; i < items.length; i++) {
let item = items[i];
const item = items[i];
if (item.is_todo) {
hasTodos = true;
break;
}
}
let seenTitles = [];
let rows = [];
const seenTitles = [];
const rows = [];
let shortIdShown = false;
for (let i = 0; i < items.length; i++) {
let item = items[i];
let row = [];
const item = items[i];
const row = [];
if (options.long) {
row.push(BaseModel.shortId(item.id));

View File

@@ -13,7 +13,7 @@ class Command extends BaseCommand {
}
async action(args) {
let folder = await Folder.save({ title: args['new-notebook'] }, { userSideValidation: true });
const folder = await Folder.save({ title: args['new-notebook'] }, { userSideValidation: true });
app().switchCurrentFolder(folder);
}
}

View File

@@ -26,7 +26,7 @@ class Command extends BaseCommand {
const ok = force ? true : await this.prompt(notes.length > 1 ? _('%d notes match this pattern. Delete them?', notes.length) : _('Delete note?'), { booleanAnswerDefault: 'n' });
if (!ok) return;
let ids = notes.map(n => n.id);
const ids = notes.map(n => n.id);
await Note.batchDelete(ids);
}
}

View File

@@ -18,8 +18,8 @@ class Command extends BaseCommand {
}
async action(args) {
let pattern = args['pattern'];
let folderTitle = args['notebook'];
const pattern = args['pattern'];
const folderTitle = args['notebook'];
let folder = null;
if (folderTitle) {

View File

@@ -23,18 +23,18 @@ class Command extends BaseCommand {
}
async action(args) {
let title = args['note'];
let propName = args['name'];
const title = args['note'];
const propName = args['name'];
let propValue = args['value'];
if (!propValue) propValue = '';
let notes = await app().loadItems(BaseModel.TYPE_NOTE, title);
const notes = await app().loadItems(BaseModel.TYPE_NOTE, title);
if (!notes.length) throw new Error(_('Cannot find "%s".', title));
for (let i = 0; i < notes.length; i++) {
this.encryptionCheck(notes[i]);
let newNote = {
const newNote = {
id: notes[i].id,
type_: notes[i].type_,
};

View File

@@ -14,30 +14,39 @@ class Command extends BaseCommand {
}
async action() {
let service = new ReportService();
let report = await service.status(Setting.value('sync.target'));
const service = new ReportService();
const report = await service.status(Setting.value('sync.target'));
for (let i = 0; i < report.length; i++) {
let section = report[i];
const section = report[i];
if (i > 0) this.stdout('');
this.stdout(`# ${section.title}`);
this.stdout('');
for (let n in section.body) {
let canRetryType = '';
for (const n in section.body) {
if (!section.body.hasOwnProperty(n)) continue;
let line = section.body[n];
this.stdout(line);
const item = section.body[n];
if (typeof item === 'object') {
canRetryType = item.canRetryType;
this.stdout(item.text);
} else {
this.stdout(item);
}
}
if (canRetryType === 'e2ee') {
this.stdout('');
this.stdout(_('To retry decryption of these items. Run `e2ee decrypt --retry-failed-items`'));
}
}
app()
.gui()
.showConsole();
app()
.gui()
.maximizeConsole();
app().gui().showConsole();
app().gui().maximizeConsole();
}
}

View File

@@ -161,9 +161,9 @@ class Command extends BaseCommand {
const sync = await syncTarget.synchronizer();
let options = {
const options = {
onProgress: report => {
let lines = Synchronizer.reportToLines(report);
const lines = Synchronizer.reportToLines(report);
if (lines.length) cliUtils.redraw(lines.join(' '));
},
onMessage: msg => {
@@ -185,7 +185,7 @@ class Command extends BaseCommand {
options.context = context;
try {
let newContext = await sync.start(options);
const newContext = await sync.start(options);
Setting.setValue(contextKey, JSON.stringify(newContext));
} catch (error) {
if (error.code == 'alreadyStarted') {

View File

@@ -20,7 +20,7 @@ class Command extends BaseCommand {
async action(args) {
let tag = null;
let options = args.options;
const options = args.options;
if (args.tag) tag = await app().loadItem(BaseModel.TYPE_TAG, args.tag);
let notes = [];
@@ -46,7 +46,7 @@ class Command extends BaseCommand {
}
} else if (command == 'list') {
if (tag) {
let notes = await Tag.notes(tag.id);
const notes = await Tag.notes(tag.id);
notes.map(note => {
let line = '';
if (options.long) {
@@ -70,7 +70,7 @@ class Command extends BaseCommand {
this.stdout(line);
});
} else {
let tags = await Tag.all();
const tags = await Tag.all();
tags.map(tag => {
this.stdout(tag.title);
});

View File

@@ -17,7 +17,7 @@ class Command extends BaseCommand {
}
async action(args) {
let folder = await app().loadItem(BaseModel.TYPE_FOLDER, args['notebook']);
const folder = await app().loadItem(BaseModel.TYPE_FOLDER, args['notebook']);
if (!folder) throw new Error(_('Cannot find "%s".', args['notebook']));
app().switchCurrentFolder(folder);
}

View File

@@ -12,7 +12,7 @@ const fs = require('fs-extra');
const baseDir = `${dirname(__dirname)}/tests/fuzzing`;
const syncDir = `${baseDir}/sync`;
const joplinAppPath = `${__dirname}/main.js`;
let syncDurations = [];
const syncDurations = [];
const fsDriver = new FsDriverNode();
Logger.fsDriver_ = fsDriver;
@@ -34,10 +34,10 @@ function createClient(id) {
}
async function createClients() {
let output = [];
let promises = [];
const output = [];
const promises = [];
for (let clientId = 0; clientId < 2; clientId++) {
let client = createClient(clientId);
const client = createClient(clientId);
promises.push(fs.remove(client.profileDir));
promises.push(
execCommand(client, 'config sync.target 2').then(() => {
@@ -2064,8 +2064,8 @@ function randomWord() {
}
function execCommand(client, command, options = {}) {
let exePath = `node ${joplinAppPath}`;
let cmd = `${exePath} --update-geolocation-disabled --env dev --log-level debug --profile ${client.profileDir} ${command}`;
const exePath = `node ${joplinAppPath}`;
const cmd = `${exePath} --update-geolocation-disabled --env dev --log-level debug --profile ${client.profileDir} ${command}`;
logger.info(`${client.id}: ${command}`);
if (options.killAfter) {
@@ -2073,7 +2073,7 @@ function execCommand(client, command, options = {}) {
}
return new Promise((resolve, reject) => {
let childProcess = exec(cmd, (error, stdout, stderr) => {
const childProcess = exec(cmd, (error, stdout, stderr) => {
if (error) {
if (error.signal == 'SIGTERM') {
resolve('Process was killed');
@@ -2096,7 +2096,7 @@ function execCommand(client, command, options = {}) {
}
async function clientItems(client) {
let itemsJson = await execCommand(client, 'dump');
const itemsJson = await execCommand(client, 'dump');
try {
return JSON.parse(itemsJson);
} catch (error) {
@@ -2105,7 +2105,7 @@ async function clientItems(client) {
}
function randomTag(items) {
let tags = [];
const tags = [];
for (let i = 0; i < items.length; i++) {
if (items[i].type_ != 5) continue;
tags.push(items[i]);
@@ -2115,7 +2115,7 @@ function randomTag(items) {
}
function randomNote(items) {
let notes = [];
const notes = [];
for (let i = 0; i < items.length; i++) {
if (items[i].type_ != 1) continue;
notes.push(items[i]);
@@ -2125,14 +2125,14 @@ function randomNote(items) {
}
async function execRandomCommand(client) {
let possibleCommands = [
const possibleCommands = [
['mkbook {word}', 40], // CREATE FOLDER
['mknote {word}', 70], // CREATE NOTE
[
async () => {
// DELETE RANDOM ITEM
let items = await clientItems(client);
let item = randomElement(items);
const items = await clientItems(client);
const item = randomElement(items);
if (!item) return;
if (item.type_ == 1) {
@@ -2150,8 +2150,8 @@ async function execRandomCommand(client) {
[
async () => {
// SYNC
let avgSyncDuration = averageSyncDuration();
let options = {};
const avgSyncDuration = averageSyncDuration();
const options = {};
if (!isNaN(avgSyncDuration)) {
if (Math.random() >= 0.5) {
options.killAfter = avgSyncDuration * Math.random();
@@ -2164,8 +2164,8 @@ async function execRandomCommand(client) {
[
async () => {
// UPDATE RANDOM ITEM
let items = await clientItems(client);
let item = randomNote(items);
const items = await clientItems(client);
const item = randomNote(items);
if (!item) return;
return execCommand(client, `set ${item.id} title "${randomWord()}"`);
@@ -2175,12 +2175,12 @@ async function execRandomCommand(client) {
[
async () => {
// ADD TAG
let items = await clientItems(client);
let note = randomNote(items);
const items = await clientItems(client);
const note = randomNote(items);
if (!note) return;
let tag = randomTag(items);
let tagTitle = !tag || Math.random() >= 0.9 ? `tag-${randomWord()}` : tag.title;
const tag = randomTag(items);
const tagTitle = !tag || Math.random() >= 0.9 ? `tag-${randomWord()}` : tag.title;
return execCommand(client, `tag add ${tagTitle} ${note.id}`);
},
@@ -2191,7 +2191,7 @@ async function execRandomCommand(client) {
let cmd = null;
while (true) {
cmd = randomElement(possibleCommands);
let r = 1 + Math.floor(Math.random() * 100);
const r = 1 + Math.floor(Math.random() * 100);
if (r <= cmd[1]) break;
}
@@ -2210,7 +2210,7 @@ function averageSyncDuration() {
}
function randomNextCheckTime() {
let output = time.unixMs() + 1000 + Math.random() * 1000 * 120;
const output = time.unixMs() + 1000 + Math.random() * 1000 * 120;
logger.info(`Next sync check: ${time.unixMsToIso(output)} (${Math.round((output - time.unixMs()) / 1000)} sec.)`);
return output;
}
@@ -2223,11 +2223,11 @@ function findItem(items, itemId) {
}
function compareItems(item1, item2) {
let output = [];
for (let n in item1) {
const output = [];
for (const n in item1) {
if (!item1.hasOwnProperty(n)) continue;
let p1 = item1[n];
let p2 = item2[n];
const p1 = item1[n];
const p2 = item2[n];
if (n == 'notes_') {
p1.sort();
@@ -2243,13 +2243,13 @@ function compareItems(item1, item2) {
}
function findMissingItems_(items1, items2) {
let output = [];
const output = [];
for (let i = 0; i < items1.length; i++) {
let item1 = items1[i];
const item1 = items1[i];
let found = false;
for (let j = 0; j < items2.length; j++) {
let item2 = items2[j];
const item2 = items2[j];
if (item1.id == item2.id) {
found = true;
break;
@@ -2269,33 +2269,33 @@ function findMissingItems(items1, items2) {
}
async function compareClientItems(clientItems) {
let itemCounts = [];
const itemCounts = [];
for (let i = 0; i < clientItems.length; i++) {
let items = clientItems[i];
const items = clientItems[i];
itemCounts.push(items.length);
}
logger.info(`Item count: ${itemCounts.join(', ')}`);
let missingItems = findMissingItems(clientItems[0], clientItems[1]);
const missingItems = findMissingItems(clientItems[0], clientItems[1]);
if (missingItems[0].length || missingItems[1].length) {
logger.error('Items are different');
logger.error(missingItems);
process.exit(1);
}
let differences = [];
let items = clientItems[0];
const differences = [];
const items = clientItems[0];
for (let i = 0; i < items.length; i++) {
let item1 = items[i];
const item1 = items[i];
for (let clientId = 1; clientId < clientItems.length; clientId++) {
let item2 = findItem(clientItems[clientId], item1.id);
const item2 = findItem(clientItems[clientId], item1.id);
if (!item2) {
logger.error(`Item not found on client ${clientId}:`);
logger.error(item1);
process.exit(1);
}
let diff = compareItems(item1, item2);
const diff = compareItems(item1, item2);
if (diff.length) {
differences.push({
item1: JSON.stringify(item1),
@@ -2315,7 +2315,7 @@ async function compareClientItems(clientItems) {
async function main() {
await fs.remove(syncDir);
let clients = await createClients();
const clients = await createClients();
let clientId = 0;
for (let i = 0; i < clients.length; i++) {
@@ -2348,7 +2348,7 @@ async function main() {
if (state == 'syncCheck') {
state = 'waitForSyncCheck';
let clientItems = [];
const clientItems = [];
// Up to 3 sync operations must be performed by each clients in order for them
// to be perfectly in sync - in order for each items to send their changes
// and get those from the other clients, and to also get changes that are
@@ -2356,12 +2356,12 @@ async function main() {
// with another one).
for (let loopCount = 0; loopCount < 3; loopCount++) {
for (let i = 0; i < clients.length; i++) {
let beforeTime = time.unixMs();
const beforeTime = time.unixMs();
await execCommand(clients[i], 'sync');
syncDurations.push(time.unixMs() - beforeTime);
if (syncDurations.length > 20) syncDurations.splice(0, 1);
if (loopCount === 2) {
let dump = await execCommand(clients[i], 'dump');
const dump = await execCommand(clients[i], 'dump');
clientItems[i] = JSON.parse(dump);
}
}

View File

@@ -20,7 +20,7 @@ class FolderListWidget extends ListWidget {
this.trimItemTitle = false;
this.itemRenderer = item => {
let output = [];
const output = [];
if (item === '-') {
output.push('-'.repeat(this.innerWidth));
} else if (item.type_ === Folder.modelType()) {
@@ -121,7 +121,7 @@ class FolderListWidget extends ListWidget {
folderHasChildren_(folders, folderId) {
for (let i = 0; i < folders.length; i++) {
let folder = folders[i];
const folder = folders[i];
if (folder.parent_id === folderId) return true;
}
return false;

View File

@@ -46,6 +46,8 @@ class NoteWidget extends TextWidget {
if (this.note_ && this.note_.encryption_applied) {
this.text = _('One or more items are currently encrypted and you may need to supply a master password. To do so please type `e2ee decrypt`. If you have already supplied the password, the encrypted items are being decrypted in the background and will be available soon.');
this.text += '\n\n';
this.text += _('You may also type `status` for more information.');
} else {
this.text = this.note_ ? `${this.note_.title}\n\n${this.note_.body}` : '';
}

View File

@@ -106,7 +106,7 @@ class StatusBarWidget extends BaseWidget {
const isSecurePrompt = !!this.promptState_.secure;
let options = {
const options = {
cancelable: true,
history: this.history,
default: this.promptState_.initialText,

View File

@@ -6,11 +6,11 @@ const MAX_WIDTH = 78;
const INDENT = ' ';
function renderTwoColumnData(options, baseIndent, width) {
let output = [];
const output = [];
const optionColWidth = getOptionColWidth(options);
for (let i = 0; i < options.length; i++) {
let option = options[i];
const option = options[i];
const flag = option[0];
const indent = baseIndent + INDENT + ' '.repeat(optionColWidth + 2);
@@ -28,7 +28,7 @@ function renderCommandHelp(cmd, width = null) {
const baseIndent = '';
let output = [];
const output = [];
output.push(baseIndent + cmd.usage());
output.push('');
output.push(wrap(cmd.description(), baseIndent + INDENT, width));
@@ -42,7 +42,7 @@ function renderCommandHelp(cmd, width = null) {
if (cmd.name() === 'config') {
const renderMetadata = md => {
let desc = [];
const desc = [];
if (md.label) {
let label = md.label();
@@ -77,7 +77,7 @@ function renderCommandHelp(cmd, width = null) {
output.push(_('Possible keys/values:'));
output.push('');
let keysValues = [];
const keysValues = [];
const keys = Setting.keys(true, 'cli');
for (let i = 0; i < keys.length; i++) {
if (keysValues.length) keysValues.push(['', '']);

View File

@@ -54,7 +54,7 @@ shimInit();
const application = app();
if (process.platform === 'win32') {
var rl = require('readline').createInterface({
const rl = require('readline').createInterface({
input: process.stdin,
output: process.stdout,
});

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

View File

@@ -34,42 +34,44 @@ locales['sr_RS'] = require('./sr_RS.json');
locales['sv'] = require('./sv.json');
locales['th_TH'] = require('./th_TH.json');
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":92};
stats['eu'] = {"percentDone":39};
stats['bs_BA'] = {"percentDone":85};
stats['bg_BG'] = {"percentDone":77};
stats['ca'] = {"percentDone":61};
stats['hr_HR'] = {"percentDone":32};
stats['cs_CZ'] = {"percentDone":94};
stats['da_DK'] = {"percentDone":85};
stats['de_DE'] = {"percentDone":100};
stats['et_EE'] = {"percentDone":76};
stats['ar'] = {"percentDone":87};
stats['eu'] = {"percentDone":37};
stats['bs_BA'] = {"percentDone":81};
stats['bg_BG'] = {"percentDone":73};
stats['ca'] = {"percentDone":58};
stats['hr_HR'] = {"percentDone":30};
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":95};
stats['eo'] = {"percentDone":44};
stats['fr_FR'] = {"percentDone":95};
stats['gl_ES'] = {"percentDone":50};
stats['it_IT'] = {"percentDone":97};
stats['nl_BE'] = {"percentDone":39};
stats['nl_NL'] = {"percentDone":97};
stats['nb_NO'] = {"percentDone":89};
stats['fa'] = {"percentDone":38};
stats['pl_PL'] = {"percentDone":75};
stats['pt_PT'] = {"percentDone":91};
stats['pt_BR'] = {"percentDone":88};
stats['ro'] = {"percentDone":39};
stats['sl_SI'] = {"percentDone":49};
stats['sv'] = {"percentDone":68};
stats['th_TH'] = {"percentDone":61};
stats['tr_TR'] = {"percentDone":92};
stats['el_GR'] = {"percentDone":93};
stats['ru_RU'] = {"percentDone":95};
stats['sr_RS'] = {"percentDone":75};
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":84};
stats['pt_PT'] = {"percentDone":98};
stats['pt_BR'] = {"percentDone":98};
stats['ro'] = {"percentDone":37};
stats['sl_SI'] = {"percentDone":46};
stats['sv'] = {"percentDone":78};
stats['th_TH'] = {"percentDone":58};
stats['vi'] = {"percentDone":94};
stats['tr_TR'] = {"percentDone":96};
stats['el_GR'] = {"percentDone":89};
stats['ru_RU'] = {"percentDone":97};
stats['sr_RS'] = {"percentDone":78};
stats['zh_CN'] = {"percentDone":97};
stats['zh_TW'] = {"percentDone":91};
stats['ja_JP'] = {"percentDone":100};
stats['ko'] = {"percentDone":97};
stats['zh_TW'] = {"percentDone":98};
stats['ja_JP'] = {"percentDone":97};
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

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