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

Compare commits

...

98 Commits

Author SHA1 Message Date
Laurent Cozic
0db9c66317 Electron release v0.10.40 2018-01-02 21:28:56 +01:00
Laurent Cozic
b22211fada All: Updated translations. Added Japanese and Chinese. 2018-01-02 21:27:38 +01:00
Laurent Cozic
79efac2f71 Merge pull request #130 from SamuelBlickle/master
Improved german translation
2018-01-02 21:22:27 +01:00
Laurent Cozic
9d7b7092f5 Merge pull request #132 from kcrt/master
Japanese localization
2018-01-02 21:22:14 +01:00
TAKAHASHI Kyohei
7e086a7730 Japanese localization 2018-01-02 14:17:39 +09:00
SamuelBlickle
3f810c71b0 improved german translations 2018-01-01 13:41:02 +01:00
Laurent Cozic
6a75451539 Merge pull request #120 from marcosvega91/fix_undo_note
Fix #119
2017-12-29 19:42:48 +01:00
Laurent Cozic
70a33f8533 CLI 0.10.84 2017-12-29 11:17:07 +01:00
Laurent Cozic
23722719f0 Merge pull request #82 from gabcoh/autocomplete
Added auto complete
2017-12-29 10:54:38 +01:00
Laurent Cozic
d499251206 Update app-gui.js
Invalidate root after inputting command
2017-12-29 10:39:50 +01:00
Laurent Cozic
ab959623aa CLI: Fixes #117: 'edit' command raising error in shell mode 2017-12-28 19:14:03 +00:00
Gabe Cohen
20632ae1c1 Correctly implement autocomplete menu with prefix 2017-12-24 12:27:25 -06:00
Gabe Cohen
3146273409 Improve autocomplete
All commands and sub commands should now autocomplete. Titles are now wrapped in spaces and quoted titles are now properly parsed.
2017-12-22 19:48:56 -06:00
marcosvega91
38c050b47e Removed duplicate editor variable 2017-12-22 15:43:01 +01:00
marcosvega91
0bf5c9ebdd Fix #119
Fix the bug that permit to undo changes from different notes.
I save the editor instance into the state and in the componentWillReceiveProps i reset the undo state
2017-12-22 14:48:44 +01:00
Laurent Cozic
0926755635 Create README_debugging.md 2017-12-17 18:54:29 +01:00
Laurent Cozic
75f8234db5 All: Added Russian translation. Thanks @rtmkrlv 2017-12-17 12:34:56 +01:00
Laurent Cozic
b8e85e1587 Merge pull request #106 from rtmkrlv/russian-locale
Russian localisation
2017-12-17 11:33:34 +00:00
Laurent Cozic
b6add857f9 Merge branch 'master' of github.com:laurent22/joplin 2017-12-17 12:30:47 +01:00
Laurent Cozic
4e41731c08 All: Updated translations 2017-12-17 12:30:32 +01:00
rtmkrlv
6ac3b8a8f9 Add files via upload
Russian localisation for Joplin. 100% done, but some corrections may be required.
2017-12-16 20:41:04 +02:00
Gabe Cohen
4756238821 Remove unused import 2017-12-15 06:39:50 -06:00
Laurent Cozic
7ab2b11c9d Update README.md
Updated list of supported languages
2017-12-15 10:07:55 +00:00
Laurent Cozic
1eb1c5914c Merge branch 'master' of github.com:laurent22/joplin 2017-12-15 08:46:39 +00:00
Laurent Cozic
5780951f7d Updated translations 2017-12-15 08:46:30 +00:00
Laurent Cozic
67e790e393 Merge pull request #95 from tbroadley/fix-typos
Fix typos
2017-12-15 07:21:26 +00:00
Thomas Broadley
0d472486ca Fix typos 2017-12-14 19:10:47 -05:00
Gabe Cohen
39c73e1649 Improve autocompletion
1. Removed autocomplete menu because it lists the entire line, not just what is
being autocompleted.

2. Autocomplete positional args first unless cursor is at - then autocomplete
long options

3. Don't autocomplete an options that is already present.

4. Other fixes
2017-12-14 07:53:49 -06:00
Gabe Cohen
3bf9d01f0a Remove unused code and retab 2017-12-14 07:01:00 -06:00
Gabe Cohen
89ef33f7ca convert spaces to tabs 2017-12-13 21:25:18 -06:00
Gabe Cohen
f71fe9a1a6 Make autocomplete more intelligent 2017-12-13 21:13:43 -06:00
Gabe Cohen
671e538740 Contain autocomplete in StatusBarWidget.js
StatusBarWidget.js now imports app itself and gets command names.
app-gui.js no longer sets the autocomplete of StatusBarWidget itself.
2017-12-11 19:31:11 -06:00
Laurent Cozic
2d218904d0 Merge branch 'master' of github.com:laurent22/joplin 2017-12-11 21:20:57 +00:00
Laurent Cozic
c3fbcb8feb Update website 2017-12-11 21:20:49 +00:00
Laurent Cozic
64031ac6cf Merge pull request #40 from Cogitri/german-locale
WIP: Add German (Germany) locale
2017-12-11 20:43:07 +00:00
Laurent Cozic
caae7f7cf8 CLI v0.10.83 2017-12-11 20:12:41 +00:00
Laurent Cozic
d56b247adf Electron release v0.10.39 2017-12-11 20:09:38 +00:00
Laurent Cozic
11bdfbde61 Fixed Readme typo 2017-12-10 16:44:43 +00:00
Laurent Cozic
a59bf55c16 Electron: Fixes #3: Paths with '.' would cause JSX compilation to fail 2017-12-10 16:06:43 +00:00
Laurent Cozic
043be1916c CLI: Display welcome message the first time the app is run. 2017-12-10 15:56:12 +00:00
Laurent Cozic
42e34b5c3b All: Fixes #87: Show warningn when deleting notebook that contains notes. 2017-12-10 14:09:12 +00:00
Laurent Cozic
931083b2e2 Electron: Fixes #86: App icon missing on Linux 2017-12-10 14:04:07 +00:00
Gabe Cohen
cda623a95c Added command auto complete
File based autocompletion is not yet implemented. This will require knowledge of
the command, and it's parameters. The autocomplete feture is pretty powerful
however, so this should not be very difficult to add.
2017-12-09 23:08:28 -06:00
Laurent Cozic
0f343bccda Electron: resolve #7: Show storage location in Options screen 2017-12-08 21:51:59 +00:00
Laurent Cozic
a513f6f3f0 Merge branch 'master' of github.com:laurent22/joplin 2017-12-08 21:42:37 +00:00
Laurent Cozic
3b4809714e iOS: version 0.10.6 2017-12-08 21:42:09 +00:00
Laurent Cozic
238b5ab9b9 CLI: version bump to fix Homebrew formula 2017-12-08 21:41:41 +00:00
Laurent Cozic
08d9e9b6aa All: Added support for HTML tags found in ENEX files: colgroup, col, ins, kbd, address, caption, var, area, map 2017-12-08 17:41:32 +00:00
Laurent Cozic
b22900eb3a Update website 2017-12-08 10:12:52 +00:00
Laurent Cozic
89fc2c4779 Removed uneeded assets 2017-12-07 23:29:47 +00:00
Laurent Cozic
353b79f5e5 Electron release v0.10.38 2017-12-07 23:25:06 +00:00
Laurent Cozic
b929b46281 Mobile: Fixed dropdown positioning 2017-12-07 23:24:14 +00:00
Laurent Cozic
5c4a536dad Android v0.10.65 2017-12-07 23:11:34 +00:00
Laurent Cozic
91e337307c Mobile: Upgrade react-native-popup-menu to fix scrolling bug 2017-12-07 23:00:22 +00:00
Laurent Cozic
2855b68ed4 Merge branch 'master' of github.com:laurent22/joplin 2017-12-07 22:56:41 +00:00
Laurent Cozic
45ca6284f9 iOS: Added safe area for iPhone X support 2017-12-07 22:42:40 +00:00
Laurent Cozic
145ee13356 Merge branch 'master' of github.com:laurent22/joplin 2017-12-07 22:42:08 +00:00
Laurent Cozic
6f97747199 Electron: Add support for file system sync 2017-12-07 22:29:02 +00:00
Laurent Cozic
f3751e4ba6 Mobile: Fixed auto-assignment of title for new notes 2017-12-07 21:32:22 +00:00
Laurent Cozic
5cd55cada6 Mobile: Made context menu scrollable 2017-12-07 21:23:41 +00:00
Laurent Cozic
bad4b2ecb8 Electron: Added dialog to export sync status 2017-12-07 21:18:18 +00:00
Laurent Cozic
7aafd63ff3 Update website 2017-12-07 19:38:24 +00:00
Laurent Cozic
3227a13035 Electron release v0.10.37 2017-12-07 18:00:28 +00:00
Laurent Cozic
027f96d100 Electron: Fixes #36: Set installation directory for Windows installer 2017-12-07 18:17:41 +00:00
Laurent Cozic
f242a3c215 Merge branch 'fix-cli' 2017-12-07 17:43:44 +00:00
Laurent Cozic
ad6c347180 CLI: Fixed shell mode 2017-12-07 18:12:46 +00:00
Rasmus Thomsen
09b9df4228 German locale: adjust articulation 2017-12-07 16:02:18 +01:00
Laurent Cozic
4f0431da55 Merge branch 'master' of github.com:laurent22/joplin 2017-12-07 13:33:26 +00:00
Laurent Cozic
b873fdd029 iOS: Release 2017-12-07 17:46:59 +00:00
Laurent Cozic
c1ff820913 Electron: Fixes #65: Display 'no notebook' message on startup. 2017-12-07 13:16:38 +00:00
Laurent Cozic
7008daf92a All: Improved handling of empty links when importing ENEX files. Fixed minor layout issues in Electron app 2017-12-07 00:57:36 +00:00
Laurent Cozic
ed914c6907 All: Improved rendering of imported ENEX tables 2017-12-07 00:35:15 +00:00
Laurent Cozic
37663bd110 Merge pull request #68 from dbinary/master
adding translate spanish Latin America
2017-12-06 23:59:22 +00:00
Laurent Cozic
f01c6aa8d1 Merge branch 'master' of github.com:laurent22/joplin 2017-12-06 23:56:18 +00:00
Laurent Cozic
8838017830 All: Updated translations 2017-12-06 23:55:22 +00:00
Laurent Cozic
1d6fb8058f Update BUILD.md 2017-12-06 23:34:56 +00:00
Laurent Cozic
bb51729bea Update BUILD.md 2017-12-06 23:34:31 +00:00
Laurent Cozic
507e7e6014 All: Improved ENEX import for web pages that have been saved as notes 2017-12-06 19:29:58 +00:00
Laurent Cozic
f42908b11c Merge branch 'sync-limits' 2017-12-05 17:30:16 +00:00
Laurent Cozic
03ec406627 All: Filter to sync target and refactored so that same code can be used by all clients 2017-12-05 19:21:01 +00:00
Laurent Cozic
c703521b6c All: Handling of unsyncable items 2017-12-05 18:56:39 +00:00
Laurent Cozic
cf97bf9a77 Updated readme 2017-12-05 08:47:48 +00:00
Laurent Cozic
304b9a582f Electron release v0.10.36 2017-12-04 23:57:30 +00:00
Laurent Cozic
4d5c4b1743 Electron: started unsynchronisable items UI 2017-12-04 23:57:13 +00:00
Laurent Cozic
4abe5d07c4 All: Handling of impossible-to-sync items (such as when they are over the size limit of the cloud provider) 2017-12-04 23:38:09 +00:00
Laurent Cozic
f6633e23f5 Minor tweaks 2017-12-04 23:01:22 +00:00
Laurent Cozic
a6d6201ecb All: Improved synchronisation process and saving of models so that reducer can deal with full objects 2017-12-04 22:58:42 +00:00
Luis Pérez
4314c392f6 adding translate spanish Latin America 2017-12-04 13:24:55 -06:00
Laurent Cozic
73e81a54b4 All: Fixed sync issue when target does not have reliable timestamps 2017-12-04 18:39:40 +00:00
Laurent Cozic
ab8c66a361 Updated tools 2017-12-04 18:16:14 +00:00
Laurent Cozic
4b55fefcb1 Cleaned up build tools 2017-12-04 17:55:06 +00:00
Laurent Cozic
0eac8b25e1 Deleted uneeded file 2017-12-03 23:06:02 +00:00
Laurent Cozic
aec556ff7d Delete package-lock.json 2017-12-03 23:03:16 +00:00
Laurent Cozic
110dc29bd4 Merge pull request #57 from mgroth0/patch-1
Add node-gyp dependency to instructions
2017-12-03 21:45:40 +00:00
Laurent Cozic
b1efea1bd9 Update BUILD.md 2017-12-03 21:44:04 +00:00
Laurent Cozic
8671467ed3 Update BUILD.md 2017-12-03 21:43:55 +00:00
Matt Groth
485ef1f2c2 Add node-gyp dependency to instructions
I was getting an error on `yarn dist` which I solved with the command `npm install -g node-gyp`.

This was the error:
```
Error: /usr/local/Cellar/node/8.9.1/bin/node exited with code 1
Output:
$ node-pre-gyp install --fallback-to-build
Failed to execute 'node-gyp clean' (Error: spawn node-gyp ENOENT)
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

Error output:
node-pre-gyp info it worked if it ends with ok
node-pre-gyp info using node-pre-gyp@0.6.38
node-pre-gyp info using node@8.9.1 | darwin | x64
node-pre-gyp http GET https://mapbox-node-binary.s3.amazonaws.com/sqlite3/v3.1.13/electron-v1.7-darwin-x64.tar.gz
node-pre-gyp http 403 https://mapbox-node-binary.s3.amazonaws.com/sqlite3/v3.1.13/electron-v1.7-darwin-x64.tar.gz
node-pre-gyp ERR! Tried to download(403): https://mapbox-node-binary.s3.amazonaws.com/sqlite3/v3.1.13/electron-v1.7-darwin-x64.tar.gz
node-pre-gyp ERR! Pre-built binaries not found for sqlite3@3.1.13 and electron@1.7.9 (electron-v1.7 ABI) (falling back to source compile with node-gyp)
node-pre-gyp http 403 status code downloading tarball https://mapbox-node-binary.s3.amazonaws.com/sqlite3/v3.1.13/electron-v1.7-darwin-x64.tar.gz
node-pre-gyp ERR! build error
node-pre-gyp ERR! stack Error: Failed to execute 'node-gyp clean' (Error: spawn node-gyp ENOENT)
node-pre-gyp ERR! stack     at ChildProcess.<anonymous> (/Users/matt/joplin/ElectronClient/app/node_modules/sqlite3/node_modules/node-pre-gyp/lib/util/compile.js:77:29)
node-pre-gyp ERR! stack     at emitOne (events.js:116:13)
node-pre-gyp ERR! stack     at ChildProcess.emit (events.js:211:7)
node-pre-gyp ERR! stack     at Process.ChildProcess._handle.onexit (internal/child_process.js:196:12)
node-pre-gyp ERR! stack     at onErrorNT (internal/child_process.js:372:16)
node-pre-gyp ERR! stack     at _combinedTickCallback (internal/process/next_tick.js:138:11)
node-pre-gyp ERR! stack     at process._tickCallback (internal/process/next_tick.js:180:9)
node-pre-gyp ERR! System Darwin 16.7.0
node-pre-gyp ERR! command "/usr/local/Cellar/node/8.9.1/bin/node" "/Users/matt/joplin/ElectronClient/app/node_modules/sqlite3/node_modules/.bin/node-pre-gyp" "install" "--fallback-to-build"
node-pre-gyp ERR! cwd /Users/matt/joplin/ElectronClient/app/node_modules/sqlite3
node-pre-gyp ERR! node -v v8.9.1
node-pre-gyp ERR! node-pre-gyp -v v0.6.38
node-pre-gyp ERR! not ok
error Command failed with exit code 1.

    at ChildProcess.childProcess.once.code (/Users/matt/joplin/ElectronClient/app/node_modules/builder-util/src/util.ts:219:14)
    at Object.onceWrapper (events.js:317:30)
    at emitTwo (events.js:126:13)
    at ChildProcess.emit (events.js:214:7)
    at maybeClose (internal/child_process.js:925:16)
    at Socket.stream.socket.on (internal/child_process.js:346:11)
    at emitOne (events.js:116:13)
    at Socket.emit (events.js:211:7)
    at Pipe._handle.close [as _onclose] (net.js:554:12)
From previous event:
    at spawn (/Users/matt/joplin/ElectronClient/app/node_modules/builder-util/src/util.ts:182:3)
    at default.map.concurrency (/Users/matt/joplin/ElectronClient/app/node_modules/electron-builder/src/util/yarn.ts:154:7)
    at runCallback (timers.js:789:20)
    at tryOnImmediate (timers.js:751:5)
    at processImmediate [as _immediateCallback] (timers.js:722:5)
From previous event:
    at /Users/matt/joplin/ElectronClient/app/node_modules/electron-builder/src/util/yarn.ts:152:27
From previous event:
    at rebuild (/Users/matt/joplin/ElectronClient/app/node_modules/electron-builder/out/util/yarn.js:94:22)
    at /Users/matt/joplin/ElectronClient/app/node_modules/electron-builder/src/util/yarn.ts:21:11
    at Generator.next (<anonymous>)
    at runCallback (timers.js:789:20)
    at tryOnImmediate (timers.js:751:5)
    at processImmediate [as _immediateCallback] (timers.js:722:5)
From previous event:
    at installOrRebuild (/Users/matt/joplin/ElectronClient/app/node_modules/electron-builder/out/util/yarn.js:32:21)
    at /Users/matt/joplin/ElectronClient/app/node_modules/electron-builder/src/packager.ts:388:7
    at Generator.next (<anonymous>)
From previous event:
    at Packager.installAppDependencies (/Users/matt/joplin/ElectronClient/app/node_modules/electron-builder/out/packager.js:433:11)
    at /Users/matt/joplin/ElectronClient/app/node_modules/electron-builder/src/packager.ts:284:20
    at Generator.next (<anonymous>)
From previous event:
    at Packager.doBuild (/Users/matt/joplin/ElectronClient/app/node_modules/electron-builder/out/packager.js:369:11)
    at /Users/matt/joplin/ElectronClient/app/node_modules/electron-builder/src/packager.ts:236:52
    at Generator.next (<anonymous>)
From previous event:
    at Packager.build (/Users/matt/joplin/ElectronClient/app/node_modules/electron-builder/out/packager.js:298:11)
    at /Users/matt/joplin/ElectronClient/app/node_modules/electron-builder/src/builder.ts:277:40
    at Generator.next (<anonymous>)
From previous event:
    at build (/Users/matt/joplin/ElectronClient/app/node_modules/electron-builder/out/builder.js:63:21)
    at then (/Users/matt/joplin/ElectronClient/app/node_modules/electron-builder/src/cli/cli.ts:49:4)
    at runCallback (timers.js:789:20)
    at tryOnImmediate (timers.js:751:5)
    at processImmediate [as _immediateCallback] (timers.js:722:5)
From previous event:
    at Object.args [as handler] (/Users/matt/joplin/ElectronClient/app/node_modules/electron-builder/src/cli/cli.ts:49:4)
    at Object.runCommand (/Users/matt/joplin/ElectronClient/app/node_modules/yargs/lib/command.js:228:22)
    at Object.parseArgs [as _parseArgs] (/Users/matt/joplin/ElectronClient/app/node_modules/yargs/yargs.js:1041:24)
    at Object.get [as argv] (/Users/matt/joplin/ElectronClient/app/node_modules/yargs/yargs.js:957:21)
    at Object.<anonymous> (/Users/matt/joplin/ElectronClient/app/node_modules/electron-builder/src/cli/cli.ts:43:15)
    at Module._compile (module.js:635:30)
    at Object.Module._extensions..js (module.js:646:10)
    at Module.load (module.js:554:32)
    at tryModuleLoad (module.js:497:12)
    at Function.Module._load (module.js:489:3)
    at Function.Module.runMain (module.js:676:10)
    at startup (bootstrap_node.js:187:16)
    at bootstrap_node.js:608:3
```
2017-12-01 21:28:13 -05:00
Laurent Cozic
b9194e94aa Electron: Fixes #84: Fields losing focus in Config screen 2017-11-08 11:41:23 +00:00
140 changed files with 11867 additions and 780 deletions

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Binary file not shown.

View File

@@ -1,2 +0,0 @@
#!/bin/bash
iconutil --convert icns macOs.iconset

View File

@@ -1,2 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<svg width="1792" height="1792" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path d="M1472 930v318q0 119-84.5 203.5t-203.5 84.5h-832q-119 0-203.5-84.5t-84.5-203.5v-832q0-119 84.5-203.5t203.5-84.5h832q63 0 117 25 15 7 18 23 3 17-9 29l-49 49q-10 10-23 10-3 0-9-2-23-6-45-6h-832q-66 0-113 47t-47 113v832q0 66 47 113t113 47h832q66 0 113-47t47-113v-254q0-13 9-22l64-64q10-10 23-10 6 0 12 3 20 8 20 29zm231-489l-814 814q-24 24-57 24t-57-24l-430-430q-24-24-24-57t24-57l110-110q24-24 57-24t57 24l263 263 647-647q24-24 57-24t57 24l110 110q24 24 24 57t-24 57z"/></svg>

Before

Width:  |  Height:  |  Size: 612 B

View File

@@ -1,2 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<svg width="1792" height="1792" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path d="M813 1299l614-614q19-19 19-45t-19-45l-102-102q-19-19-45-19t-45 19l-467 467-211-211q-19-19-45-19t-45 19l-102 102q-19 19-19 45t19 45l358 358q19 19 45 19t45-19zm851-883v960q0 119-84.5 203.5t-203.5 84.5h-960q-119 0-203.5-84.5t-84.5-203.5v-960q0-119 84.5-203.5t203.5-84.5h960q119 0 203.5 84.5t84.5 203.5z"/></svg>

Before

Width:  |  Height:  |  Size: 447 B

View File

@@ -1,2 +0,0 @@
<?xml version='1.0' encoding='utf-8'?>
<svg viewBox='0 0 1792 1792' xmlns='http://www.w3.org/2000/svg'><path d='M1312 256h-832q-66 0-113 47t-47 113v832q0 66 47 113t113 47h832q66 0 113-47t47-113v-832q0-66-47-113t-113-47zm288 160v832q0 119-84.5 203.5t-203.5 84.5h-832q-119 0-203.5-84.5t-84.5-203.5v-832q0-119 84.5-203.5t203.5-84.5h832q119 0 203.5 84.5t84.5 203.5z'/></svg>

Before

Width:  |  Height:  |  Size: 370 B

View File

@@ -1,18 +1,21 @@
# General information
- All the applications share the same library, which, for historical reasons, is in ReactNativeClient/lib. This library is copied to the relevant directories when builing each app.
- The translations are built by running CliClient/build-translation.sh. For this reasons, it's generally better to get the CLI app to build first so that everything is setup correctly.
- All the applications share the same library, which, for historical reasons, is in ReactNativeClient/lib. This library is copied to the relevant directories when building each app.
- The translations are built by running CliClient/build-translation.sh. You normally don't need to run this if you haven't updated the translation since the compiled files are on the repository.
## macOS dependencies
brew install yarn node xgettext
echo 'export PATH="/usr/local/opt/gettext/bin:$PATH"' >> ~/.bash_profile
source ~/.bash_profile
brew install yarn node
echo 'export PATH="/usr/local/opt/gettext/bin:$PATH"' >> ~/.bash_profile
source ~/.bash_profile
If you get a node-gyp related error you might need to manually install it: `npm install -g node-gyp`
## Linux and Windows dependencies
## Linux and Windows (WSL) dependencies
- Install yarn - https://yarnpkg.com/lang/en/docs/install/
- Install node v8.x (check with `node --version`) - https://nodejs.org/en/
- If you get a node-gyp related error you might need to manually install it: `npm install -g node-gyp`
# Building the Electron application
@@ -31,8 +34,17 @@ From `/ElectronClient` you can also run `run.sh` to run the app for testing.
# Building the Mobile application
From `/ReactNativeClient`, run `npm install`, then `react-native run-ios` or `react-native run-android`.
First you need to setup React Native to build projects with native code. For this, follow the instructions on the [Get Started](https://facebook.github.io/react-native/docs/getting-started.html) tutorial, in the "Building Projects with Native Code" tab.
Then, from `/ReactNativeClient`, run `npm install`, then `react-native run-ios` or `react-native run-android`.
# Building the Terminal application
From `/CliClient`, run `npm install` then run `run.sh`. If you get an error about `xgettext`, comment out the command `node build-translation.js --silent` in build.sh
```
cd CliClient
npm install
./build.sh
rsync -aP ../ReactNativeClient/locales/ build/locales/
```
Run `run.sh` to start the application for testing.

View File

@@ -80,6 +80,14 @@ class AppGui {
await this.renderer_.renderRoot();
}
termSaveState() {
return this.term().saveState();
}
termRestoreState(state) {
return this.term().restoreState(state);
}
prompt(initialText = '', promptString = ':') {
return this.widget('statusBar').prompt(initialText, promptString);
}
@@ -548,6 +556,10 @@ class AppGui {
}
this.widget('console').scrollBottom();
// Invalidate so that the screen is redrawn in case inputting a command has moved
// the GUI up (in particular due to autocompletion), it's moved back to the right position.
this.widget('root').invalidate();
}
async updateFolderList() {
@@ -826,4 +838,4 @@ class AppGui {
AppGui.INPUT_MODE_NORMAL = 1;
AppGui.INPUT_MODE_META = 2;
module.exports = AppGui;
module.exports = AppGui;

View File

@@ -177,22 +177,33 @@ class Application extends BaseApplication {
await doExit();
}
commands() {
if (this.allCommandsLoaded_) return this.commands_;
commands(uiType = null) {
if (!this.allCommandsLoaded_) {
fs.readdirSync(__dirname).forEach((path) => {
if (path.indexOf('command-') !== 0) return;
const ext = fileExtension(path)
if (ext != 'js') return;
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();
if (!cmd.enabled()) return;
cmd = this.setupCommand(cmd);
this.commands_[cmd.name()] = cmd;
});
let CommandClass = require('./' + path);
let cmd = new CommandClass();
if (!cmd.enabled()) return;
cmd = this.setupCommand(cmd);
this.commands_[cmd.name()] = cmd;
});
this.allCommandsLoaded_ = true;
}
this.allCommandsLoaded_ = true;
if (uiType !== null) {
let temp = [];
for (let n in this.commands_) {
if (!this.commands_.hasOwnProperty(n)) continue;
const c = this.commands_[n];
if (!c.supportsUi(uiType)) continue;
temp[n] = c;
}
return temp;
}
return this.commands_;
}
@@ -246,9 +257,13 @@ class Application extends BaseApplication {
try {
CommandClass = require(__dirname + '/command-' + name + '.js');
} catch (error) {
let e = new Error('No such command: ' + name);
e.type = 'notFound';
throw e;
if (error.message && error.message.indexOf('Cannot find module') >= 0) {
let e = new Error(_('No such command: %s', name));
e.type = 'notFound';
throw e;
} else {
throw error;
}
}
let cmd = new CommandClass();
@@ -268,7 +283,10 @@ class Application extends BaseApplication {
exit: () => {},
showModalOverlay: (text) => {},
hideModalOverlay: () => {},
stdoutMaxWidth: () => { return 78; }
stdoutMaxWidth: () => { return 78; },
forceRender: () => {},
termSaveState: () => {},
termRestoreState: (state) => {},
};
}
@@ -306,6 +324,8 @@ class Application extends BaseApplication {
if (argv.length) {
this.gui_ = this.dummyGui();
this.currentFolder_ = await Folder.load(Setting.value('activeFolderId'));
try {
await this.execCommand(argv);
} catch (error) {

View File

@@ -0,0 +1,185 @@
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');
async function handleAutocompletionPromise(line) {
// Auto-complete the command name
const names = await app().commandNames();
let words = getArguments(line);
//If there is only one word and it is not already a command name then you
//should look for commmands it could be
if (words.length == 1) {
if (names.indexOf(words[0]) === -1) {
let x = names.filter((n) => n.indexOf(words[0]) === 0);
if (x.length === 1) {
return x[0] + ' ';
}
return x.length > 0 ? x.map((a) => a + ' ') : line;
} else {
return line;
}
}
//There is more than one word and it is a command
const metadata = (await app().commandMetadata())[words[0]];
//If for some reason this command does not have any associated metadata
//just don't autocomplete. However, this should not happen.
if (metadata === undefined) {
return line;
}
//complete an option
let next = words.length > 1 ? words[words.length - 1] : '';
let l = [];
if (next[0] === '-') {
for (let i = 0; i<metadata.options.length; i++) {
const options = metadata.options[i][0].split(' ');
//if there are multiple options then they will be seperated by comma and
//space. The comma should be removed
if (options[0][options[0].length - 1] === ',') {
options[0] = options[0].slice(0, -1);
}
if (words.includes(options[0]) || words.includes(options[1])) {
continue;
}
//First two elements are the flag and the third is the description
//Only autocomplete long
if (options.length > 1 && options[1].indexOf(next) === 0) {
l.push(options[1]);
} else if (options[0].indexOf(next) === 0) {
l.push(options[2]);
}
}
if (l.length === 0) {
return line;
}
let ret = l.map(a=>toCommandLine(a));
ret.prefix = toCommandLine(words.slice(0, -1)) + ' ';
return ret;
}
//Complete an argument
//Determine the number of positional arguments by counting the number of
//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)['_'];
cmdUsage.splice(0, 1);
if (cmdUsage.length >= positionalArgs) {
let argName = cmdUsage[positionalArgs - 1];
argName = cliUtils.parseCommandArg(argName).name;
if (argName == 'note' || argName == 'note-pattern' && app().currentFolder()) {
const notes = await Note.previews(app().currentFolder().id, { titlePattern: next + '*' });
l.push(...notes.map((n) => n.title));
}
if (argName == 'notebook') {
const folders = await Folder.search({ titlePattern: next + '*' });
l.push(...folders.map((n) => n.title));
}
if (argName == 'tag') {
let tags = await Tag.search({ titlePattern: next + '*' });
l.push(...tags.map((n) => n.title));
}
if (argName == 'tag-command') {
let c = filterList(['add', 'remove', 'list'], next);
l.push(...c);
}
if (argName == 'todo-command') {
let 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));
ret.prefix = toCommandLine(words.slice(0, -1)) + ' ';
return ret;
}
return line;
}
function handleAutocompletion(str, callback) {
handleAutocompletionPromise(str).then(function(res) {
callback(undefined, res);
});
}
function toCommandLine(args) {
if (Array.isArray(args)) {
return args.map(function(a) {
if(a.indexOf('"') !== -1 || a.indexOf(' ') !== -1) {
return "'" + a + "'";
} else if (a.indexOf("'") !== -1) {
return '"' + a + '"';
} else {
return a;
}
}).join(' ');
} else {
if(args.indexOf('"') !== -1 || args.indexOf(' ') !== -1) {
return "'" + args + "' ";
} else if (args.indexOf("'") !== -1) {
return '"' + args + '" ';
} else {
return args + ' ';
}
}
}
function getArguments(line) {
let inSingleQuotes = false;
let inDoubleQuotes = false;
let currentWord = '';
let parsed = [];
for(let i = 0; i<line.length; i++) {
if(line[i] === '"') {
if(inDoubleQuotes) {
inDoubleQuotes = false;
//maybe push word to parsed?
//currentWord += '"';
} else {
inDoubleQuotes = true;
//currentWord += '"';
}
} else if(line[i] === "'") {
if(inSingleQuotes) {
inSingleQuotes = false;
//maybe push word to parsed?
//currentWord += "'";
} else {
inSingleQuotes = true;
//currentWord += "'";
}
} else if (/\s/.test(line[i]) &&
!(inDoubleQuotes || inSingleQuotes)) {
if (currentWord !== '') {
parsed.push(currentWord);
currentWord = '';
}
} else {
currentWord += line[i];
}
}
if (!(inSingleQuotes || inDoubleQuotes) && /\s/.test(line[line.length - 1])) {
parsed.push('');
} else {
parsed.push(currentWord);
}
return parsed;
}
function filterList(list, next) {
let output = [];
for (let i = 0; i < list.length; i++) {
if (list[i].indexOf(next) !== 0) continue;
output.push(list[i]);
}
return output;
}
module.exports = { handleAutocompletion };

View File

@@ -76,12 +76,12 @@ class Command extends BaseCommand {
app().gui().showModalOverlay(_('Starting to edit note. Close the editor to get back to the prompt.'));
await app().gui().forceRender();
const termState = app().gui().term().saveState();
const termState = app().gui().termSaveState();
const spawnSync = require('child_process').spawnSync;
spawnSync(editorPath, editorArgs, { stdio: 'inherit' });
app().gui().term().restoreState(termState);
app().gui().termRestoreState(termState);
app().gui().hideModalOverlay();
app().gui().forceRender();

View File

@@ -12,6 +12,10 @@ class Command extends BaseCommand {
return _('Exits the application.');
}
compatibleUis() {
return ['gui'];
}
async action(args) {
await app().exit();
}

View File

@@ -18,7 +18,7 @@ class Command extends BaseCommand {
}
allCommands() {
const commands = app().commands();
const commands = app().commands(app().uiType());
let output = [];
for (let n in commands) {
if (!commands.hasOwnProperty(n)) continue;
@@ -65,7 +65,7 @@ class Command extends BaseCommand {
} else {
const commandNames = this.allCommands().map((a) => a.name());
this.stdout(_('Type `help [command]` for more information about a command.'));
this.stdout(_('Type `help [command]` for more information about a command; or type `help all` for the complete usage information.'));
this.stdout('');
this.stdout(_('The possible commands are:'));
this.stdout('');

View File

@@ -29,7 +29,7 @@ class Command extends BaseCommand {
const folder = await app().loadItem(BaseModel.TYPE_FOLDER, pattern);
if (!folder) throw new Error(_('Cannot find "%s".', pattern));
const ok = force ? true : await this.prompt(_('Delete notebook "%s"?', folder.title), { booleanAnswerDefault: 'n' });
const ok = force ? true : await this.prompt(_('Delete notebook? All notes within this notebook will also be deleted.'), { booleanAnswerDefault: 'n' });
if (!ok) return;
await Folder.delete(folder.id);

View File

@@ -2,6 +2,7 @@ const { BaseCommand } = require('./base-command.js');
const { app } = require('./app.js');
const { _ } = require('lib/locale.js');
const { BaseModel } = require('lib/base-model.js');
const { Database } = require('lib/database.js');
const { Folder } = require('lib/models/folder.js');
const { Note } = require('lib/models/note.js');
const { BaseItem } = require('lib/models/base-item.js');
@@ -12,16 +13,16 @@ class Command extends BaseCommand {
return 'set <note> <name> [value]';
}
enabled() {
return false;
}
description() {
return _('Sets the property <name> of the given <note> to the given [value].');
}
const fields = Note.fields();
const s = [];
for (let i = 0; i < fields.length; i++) {
const f = fields[i];
if (f.name === 'id') continue;
s.push(f.name + ' (' + Database.enumName('fieldType', f.type) + ')');
}
hidden() {
return true;
return _('Sets the property <name> of the given <note> to the given [value]. Possible properties are:\n\n%s', s.join(', '));
}
async action(args) {

View File

@@ -32,7 +32,6 @@ class Command extends BaseCommand {
options() {
return [
['--target <target>', _('Sync to provided target (defaults to sync.target config value)')],
['--random-failures', 'For debugging purposes. Do not use.'],
];
}
@@ -140,7 +139,6 @@ class Command extends BaseCommand {
cliUtils.redrawDone();
this.stdout(msg);
},
randomFailures: args.options['random-failures'] === true,
};
this.stdout(_('Synchronisation target: %s (%s)', Setting.enumOptionLabel('sync.target', this.syncTargetId_), this.syncTargetId_));

View File

@@ -18,8 +18,8 @@ class Command extends BaseCommand {
return { data: autocompleteFolders };
}
enabled() {
return false;
compatibleUis() {
return ['cli'];
}
async action(args) {

View File

@@ -1,5 +1,6 @@
const Note = require('lib/models/note.js').Note;
const TextWidget = require('tkwidgets/TextWidget.js');
const { _ } = require('lib/locale.js');
class NoteWidget extends TextWidget {
@@ -32,8 +33,15 @@ class NoteWidget extends TextWidget {
this.reloadNote();
}
welcomeText() {
return _('Welcome to Joplin!\n\nType `:help shortcuts` for the list of keyboard shortcuts, or just `:help` for usage information.\n\nFor example, to create a notebook press `mb`; to create a note press `mn`.');
}
reloadNote() {
if (this.noteId_) {
if (!this.noteId_ && !this.notes.length) {
this.text = this.welcomeText();
this.scrollTop = 0;
} else if (this.noteId_) {
this.doAsync('loadNote', async () => {
this.note_ = await Note.load(this.noteId_);
this.text = this.note_ ? this.note_.title + "\n\n" + this.note_.body : '';

View File

@@ -2,6 +2,7 @@ const BaseWidget = require('tkwidgets/BaseWidget.js');
const chalk = require('chalk');
const termutils = require('tkwidgets/framework/termutils.js');
const stripAnsi = require('strip-ansi');
const { handleAutocompletion } = require('../autocompletion.js');
class StatusBarWidget extends BaseWidget {
@@ -108,6 +109,9 @@ class StatusBarWidget extends BaseWidget {
cancelable: true,
history: this.history,
default: this.promptState_.initialText,
autoComplete: handleAutocompletion,
autoCompleteHint : true,
autoCompleteMenu : true,
};
if ('cursorPosition' in this.promptState_) options.cursorPosition = this.promptState_.cursorPosition;
@@ -159,4 +163,4 @@ class StatusBarWidget extends BaseWidget {
}
module.exports = StatusBarWidget;
module.exports = StatusBarWidget;

View File

@@ -1,3 +0,0 @@
#/bin/bash
CLIENT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
NODE_PATH="$CLIENT_DIR/build" node "$CLIENT_DIR/build/build-translation.js" --silent

View File

@@ -1,4 +0,0 @@
#!/bin/bash
set -e
ROOT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
"$ROOT_DIR/build.sh" && NODE_PATH="$ROOT_DIR/build" node "$ROOT_DIR/build/build-website.js"

View File

@@ -6,7 +6,4 @@ BUILD_DIR="$ROOT_DIR/build"
rsync -a --exclude "node_modules/" "$ROOT_DIR/app/" "$BUILD_DIR/"
rsync -a "$ROOT_DIR/../ReactNativeClient/lib/" "$BUILD_DIR/lib/"
cp "$ROOT_DIR/package.json" "$BUILD_DIR"
chmod 755 "$BUILD_DIR/main.js"
# cd "$BUILD_DIR"
# node build-translation.js --silent
chmod 755 "$BUILD_DIR/main.js"

View File

@@ -1,6 +0,0 @@
#!/bin/bash
CLIENT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
"$CLIENT_DIR/publish.sh"
npm install -g joplin

1185
CliClient/locales/de_DE.po Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -100,6 +100,10 @@ msgstr ""
msgid "Cancelling background synchronisation... Please wait."
msgstr ""
#, javascript-format
msgid "No such command: %s"
msgstr ""
#, javascript-format
msgid "The command \"%s\" is only available in GUI mode"
msgstr ""
@@ -204,7 +208,9 @@ msgstr ""
msgid "Shortcuts are not available in CLI mode."
msgstr ""
msgid "Type `help [command]` for more information about a command."
msgid ""
"Type `help [command]` for more information about a command; or type `help "
"all` for the complete usage information."
msgstr ""
msgid "The possible commands are:"
@@ -339,8 +345,7 @@ msgstr ""
msgid "Deletes the notebook without asking for confirmation."
msgstr ""
#, javascript-format
msgid "Delete notebook \"%s\"?"
msgid "Delete notebook? All notes within this notebook will also be deleted."
msgstr ""
msgid "Deletes the notes matching <note-pattern>."
@@ -359,7 +364,12 @@ msgstr ""
msgid "Searches for the given <pattern> in all the notes."
msgstr ""
msgid "Sets the property <name> of the given <note> to the given [value]."
#, javascript-format
msgid ""
"Sets the property <name> of the given <note> to the given [value]. Possible "
"properties are:\n"
"\n"
"%s"
msgstr ""
msgid "Displays summary about the notes and notebooks."
@@ -469,6 +479,15 @@ msgstr ""
msgid "Search:"
msgstr ""
msgid ""
"Welcome to Joplin!\n"
"\n"
"Type `:help shortcuts` for the list of keyboard shortcuts, or just `:help` "
"for usage information.\n"
"\n"
"For example, to create a notebook press `mb`; to create a note press `mn`."
msgstr ""
msgid "File"
msgstr ""
@@ -508,6 +527,9 @@ msgstr ""
msgid "Tools"
msgstr ""
msgid "Synchronisation status"
msgstr ""
msgid "Options"
msgstr ""
@@ -530,6 +552,74 @@ msgstr ""
msgid "Cancel"
msgstr ""
#, javascript-format
msgid "Notes and settings are stored in: %s"
msgstr ""
msgid "Save"
msgstr ""
msgid ""
"Disabling encryption means *all* your notes and attachments are going to be "
"re-synchronised and sent unencrypted to the sync target. Do you wish to "
"continue?"
msgstr ""
msgid ""
"Enabling encryption means *all* your notes and attachments are going to be "
"re-synchronised and sent encrypted to the sync target. Do not lose the "
"password as, for security purposes, this will be the *only* way to decrypt "
"the data! To enable encryption, please enter your password below."
msgstr ""
msgid "Disable encryption"
msgstr ""
msgid "Enable encryption"
msgstr ""
msgid "Master Keys"
msgstr ""
msgid "Active"
msgstr ""
msgid "ID"
msgstr ""
msgid "Source"
msgstr ""
msgid "Created"
msgstr ""
msgid "Updated"
msgstr ""
msgid "Password"
msgstr ""
msgid "Password OK"
msgstr ""
msgid ""
"Note: Only one master key is going to be used for encryption (the one marked "
"as \"active\"). Any of the keys might be used for decryption, depending on "
"how the notes or notebooks were originally encrypted."
msgstr ""
msgid "Status"
msgstr ""
msgid "Encryption is:"
msgstr ""
msgid "Enabled"
msgstr ""
msgid "Disabled"
msgstr ""
msgid "Back"
msgstr ""
@@ -568,6 +658,18 @@ msgstr ""
msgid "Layout"
msgstr ""
msgid "Some items cannot be synchronised."
msgstr ""
msgid "View them now"
msgstr ""
msgid "Some items cannot be decrypted."
msgstr ""
msgid "Set the password"
msgstr ""
msgid "Add or remove tags"
msgstr ""
@@ -583,6 +685,10 @@ msgstr ""
msgid "No notes in here. Create one by clicking on \"New note\"."
msgstr ""
msgid ""
"There is currently no notebook. Create one by clicking on \"New notebook\"."
msgstr ""
#, javascript-format
msgid "Unsupported link or message: %s"
msgstr ""
@@ -605,7 +711,10 @@ msgstr ""
msgid "Import"
msgstr ""
msgid "Delete notebook?"
msgid "Synchronisation Status"
msgstr ""
msgid "Encryption Options"
msgstr ""
msgid "Remove this tag from all the notes?"
@@ -629,6 +738,9 @@ msgstr ""
msgid "Searches"
msgstr ""
msgid "Please select where the sync status should be exported to"
msgstr ""
#, javascript-format
msgid "Usage: %s"
msgstr ""
@@ -736,14 +848,6 @@ msgstr ""
msgid "Cannot move note to \"%s\" notebook"
msgstr ""
msgid "File system synchronisation target directory"
msgstr ""
msgid ""
"The path to synchronise with when file system synchronisation is enabled. "
"See `sync.target`."
msgstr ""
msgid "Text editor"
msgstr ""
@@ -779,9 +883,6 @@ msgstr ""
msgid "Synchronisation interval"
msgstr ""
msgid "Disabled"
msgstr ""
#, javascript-format
msgid "%d minutes"
msgstr ""
@@ -808,10 +909,25 @@ msgid ""
"`sync.2.path` to specify the target directory."
msgstr ""
msgid "Directory to synchronise with (absolute path)"
msgstr ""
msgid ""
"The path to synchronise with when file system synchronisation is enabled. "
"See `sync.target`."
msgstr ""
#, javascript-format
msgid "Invalid option value: \"%s\". Possible values are: %s."
msgstr ""
msgid "Items that cannot be synchronised"
msgstr ""
#, javascript-format
msgid "\"%s\": \"%s\""
msgstr ""
msgid "Sync status (synced items / total items)"
msgstr ""
@@ -854,9 +970,6 @@ msgstr ""
msgid "Log"
msgstr ""
msgid "Status"
msgstr ""
msgid "Export Debug Report"
msgstr ""

File diff suppressed because it is too large Load Diff

1151
CliClient/locales/es_ES.po Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -100,6 +100,10 @@ msgstr "o"
msgid "Cancelling background synchronisation... Please wait."
msgstr "Annulation de la synchronisation... Veuillez patienter."
#, fuzzy, javascript-format
msgid "No such command: %s"
msgstr "Commande invalide : \"%s\""
#, javascript-format
msgid "The command \"%s\" is only available in GUI mode"
msgstr ""
@@ -217,7 +221,10 @@ msgstr "Affiche les informations d'utilisation."
msgid "Shortcuts are not available in CLI mode."
msgstr "Les raccourcis ne sont pas disponible en mode de ligne de commande."
msgid "Type `help [command]` for more information about a command."
#, fuzzy
msgid ""
"Type `help [command]` for more information about a command; or type `help "
"all` for the complete usage information."
msgstr "Tapez `help [command]` pour plus d'information sur une commande."
msgid "The possible commands are:"
@@ -370,9 +377,8 @@ msgstr "Supprimer le carnet."
msgid "Deletes the notebook without asking for confirmation."
msgstr "Supprimer le carnet sans demander la confirmation."
#, javascript-format
msgid "Delete notebook \"%s\"?"
msgstr "Supprimer le carnet \"%s\" ?"
msgid "Delete notebook? All notes within this notebook will also be deleted."
msgstr ""
msgid "Deletes the notes matching <note-pattern>."
msgstr "Supprimer les notes correspondants à <note-pattern>."
@@ -390,7 +396,12 @@ msgstr "Supprimer la note ?"
msgid "Searches for the given <pattern> in all the notes."
msgstr "Chercher le motif <pattern> dans toutes les notes."
msgid "Sets the property <name> of the given <note> to the given [value]."
#, fuzzy, javascript-format
msgid ""
"Sets the property <name> of the given <note> to the given [value]. Possible "
"properties are:\n"
"\n"
"%s"
msgstr "Assigner la valeur [value] à la propriété <name> de la <note> donnée."
msgid "Displays summary about the notes and notebooks."
@@ -519,6 +530,15 @@ msgstr ""
msgid "Search:"
msgstr "Recherche :"
msgid ""
"Welcome to Joplin!\n"
"\n"
"Type `:help shortcuts` for the list of keyboard shortcuts, or just `:help` "
"for usage information.\n"
"\n"
"For example, to create a notebook press `mb`; to create a note press `mn`."
msgstr ""
msgid "File"
msgstr "Fichier"
@@ -558,6 +578,10 @@ msgstr "Chercher dans toutes les notes"
msgid "Tools"
msgstr "Outils"
#, fuzzy
msgid "Synchronisation status"
msgstr "Cible de la synchronisation"
msgid "Options"
msgstr "Options"
@@ -580,6 +604,77 @@ msgstr "OK"
msgid "Cancel"
msgstr "Annulation"
#, javascript-format
msgid "Notes and settings are stored in: %s"
msgstr ""
msgid "Save"
msgstr ""
msgid ""
"Disabling encryption means *all* your notes and attachments are going to be "
"re-synchronised and sent unencrypted to the sync target. Do you wish to "
"continue?"
msgstr ""
msgid ""
"Enabling encryption means *all* your notes and attachments are going to be "
"re-synchronised and sent encrypted to the sync target. Do not lose the "
"password as, for security purposes, this will be the *only* way to decrypt "
"the data! To enable encryption, please enter your password below."
msgstr ""
msgid "Disable encryption"
msgstr ""
msgid "Enable encryption"
msgstr ""
msgid "Master Keys"
msgstr ""
msgid "Active"
msgstr ""
msgid "ID"
msgstr ""
msgid "Source"
msgstr ""
#, fuzzy
msgid "Created"
msgstr "Créés : %d."
#, fuzzy
msgid "Updated"
msgstr "Mis à jour : %d."
msgid "Password"
msgstr ""
msgid "Password OK"
msgstr ""
msgid ""
"Note: Only one master key is going to be used for encryption (the one marked "
"as \"active\"). Any of the keys might be used for decryption, depending on "
"how the notes or notebooks were originally encrypted."
msgstr ""
msgid "Status"
msgstr "État"
msgid "Encryption is:"
msgstr ""
#, fuzzy
msgid "Enabled"
msgstr "Désactivé"
msgid "Disabled"
msgstr "Désactivé"
msgid "Back"
msgstr "Retour"
@@ -621,6 +716,20 @@ msgstr "Définir ou modifier alarme"
msgid "Layout"
msgstr "Disposition"
#, fuzzy
msgid "Some items cannot be synchronised."
msgstr "Impossible d'initialiser la synchronisation."
msgid "View them now"
msgstr ""
#, fuzzy
msgid "Some items cannot be decrypted."
msgstr "Impossible d'initialiser la synchronisation."
msgid "Set the password"
msgstr ""
msgid "Add or remove tags"
msgstr "Gérer les étiquettes"
@@ -637,6 +746,13 @@ msgid "No notes in here. Create one by clicking on \"New note\"."
msgstr ""
"Pas de notes ici. Créez-en une en pressant le bouton \"Nouvelle note\"."
#, fuzzy
msgid ""
"There is currently no notebook. Create one by clicking on \"New notebook\"."
msgstr ""
"Il n'y a pour l'instant aucun carnet. Créez-en un en cliquant sur le bouton "
"(+)"
#, javascript-format
msgid "Unsupported link or message: %s"
msgstr "Lien ou message non géré : %s"
@@ -660,8 +776,12 @@ msgstr "Connexion OneDrive"
msgid "Import"
msgstr "Importer"
msgid "Delete notebook?"
msgstr "Supprimer le carnet ?"
#, fuzzy
msgid "Synchronisation Status"
msgstr "Cible de la synchronisation"
msgid "Encryption Options"
msgstr ""
msgid "Remove this tag from all the notes?"
msgstr "Enlever cette étiquette de toutes les notes ?"
@@ -684,6 +804,10 @@ msgstr "Étiquettes"
msgid "Searches"
msgstr "Recherches"
#, fuzzy
msgid "Please select where the sync status should be exported to"
msgstr "Veuillez d'abord sélectionner un carnet."
#, javascript-format
msgid "Usage: %s"
msgstr "Utilisation : %s"
@@ -793,16 +917,6 @@ msgstr "Impossible de copier la note vers le carnet \"%s\""
msgid "Cannot move note to \"%s\" notebook"
msgstr "Impossible de déplacer la note vers le carnet \"%s\""
msgid "File system synchronisation target directory"
msgstr "Cible de la synchronisation sur le disque dur"
msgid ""
"The path to synchronise with when file system synchronisation is enabled. "
"See `sync.target`."
msgstr ""
"Le chemin du répertoire avec lequel synchroniser lorsque la synchronisation "
"par système de fichier est activée. Voir `sync.target`."
msgid "Text editor"
msgstr "Éditeur de texte"
@@ -840,9 +954,6 @@ msgstr "Enregistrer l'emplacement avec les notes"
msgid "Synchronisation interval"
msgstr "Intervalle de synchronisation"
msgid "Disabled"
msgstr "Désactivé"
#, javascript-format
msgid "%d minutes"
msgstr "%d minutes"
@@ -871,10 +982,27 @@ msgstr ""
"La cible avec laquelle synchroniser. Pour synchroniser avec le système de "
"fichier, veuillez spécifier le répertoire avec `sync.2.path`."
msgid "Directory to synchronise with (absolute path)"
msgstr ""
msgid ""
"The path to synchronise with when file system synchronisation is enabled. "
"See `sync.target`."
msgstr ""
"Le chemin du répertoire avec lequel synchroniser lorsque la synchronisation "
"par système de fichier est activée. Voir `sync.target`."
#, javascript-format
msgid "Invalid option value: \"%s\". Possible values are: %s."
msgstr "Option invalide: \"%s\". Les valeurs possibles sont : %s."
msgid "Items that cannot be synchronised"
msgstr ""
#, javascript-format
msgid "\"%s\": \"%s\""
msgstr ""
msgid "Sync status (synced items / total items)"
msgstr "Status de la synchronisation (objets synchro. / total)"
@@ -919,9 +1047,6 @@ msgstr "Supprimer ces notes ?"
msgid "Log"
msgstr "Journal"
msgid "Status"
msgstr "État"
msgid "Export Debug Report"
msgstr "Exporter rapport de débogage"
@@ -1006,6 +1131,15 @@ msgstr ""
msgid "Welcome"
msgstr "Bienvenue"
#~ msgid "Delete notebook?"
#~ msgstr "Supprimer le carnet ?"
#~ msgid "Delete notebook \"%s\"?"
#~ msgstr "Supprimer le carnet \"%s\" ?"
#~ msgid "File system synchronisation target directory"
#~ msgstr "Cible de la synchronisation sur le disque dur"
#~ msgid "Set or clear alarm:"
#~ msgstr "Définir ou modifier alarme :"
@@ -1078,10 +1212,6 @@ msgstr "Bienvenue"
#~ msgid "Show/Hide the console"
#~ msgstr "Quitter le logiciel."
#, fuzzy
#~ msgid "Last command: %s"
#~ msgstr "Commande invalide : \"%s\""
#~ msgid "Done editing."
#~ msgstr "Edition terminée."
@@ -1183,12 +1313,6 @@ msgstr "Bienvenue"
#~ "Tous les ports sont en cours d'utilisation. Veuillez signaler ce problème "
#~ "sur %s"
#~ msgid ""
#~ "There is currently no notebook. Create one by clicking on the (+) button."
#~ msgstr ""
#~ "Il n'y a pour l'instant aucun carnet. Créez-en un en cliquant sur le "
#~ "bouton (+)"
#~ msgid "Synchronizing with directory \"%s\""
#~ msgstr "Synchronisation avec dossier \"%s\""

1147
CliClient/locales/hr_HR.po Normal file

File diff suppressed because it is too large Load Diff

1141
CliClient/locales/it_IT.po Normal file

File diff suppressed because it is too large Load Diff

1134
CliClient/locales/ja_JP.po Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -100,6 +100,10 @@ msgstr ""
msgid "Cancelling background synchronisation... Please wait."
msgstr ""
#, javascript-format
msgid "No such command: %s"
msgstr ""
#, javascript-format
msgid "The command \"%s\" is only available in GUI mode"
msgstr ""
@@ -204,7 +208,9 @@ msgstr ""
msgid "Shortcuts are not available in CLI mode."
msgstr ""
msgid "Type `help [command]` for more information about a command."
msgid ""
"Type `help [command]` for more information about a command; or type `help "
"all` for the complete usage information."
msgstr ""
msgid "The possible commands are:"
@@ -339,8 +345,7 @@ msgstr ""
msgid "Deletes the notebook without asking for confirmation."
msgstr ""
#, javascript-format
msgid "Delete notebook \"%s\"?"
msgid "Delete notebook? All notes within this notebook will also be deleted."
msgstr ""
msgid "Deletes the notes matching <note-pattern>."
@@ -359,7 +364,12 @@ msgstr ""
msgid "Searches for the given <pattern> in all the notes."
msgstr ""
msgid "Sets the property <name> of the given <note> to the given [value]."
#, javascript-format
msgid ""
"Sets the property <name> of the given <note> to the given [value]. Possible "
"properties are:\n"
"\n"
"%s"
msgstr ""
msgid "Displays summary about the notes and notebooks."
@@ -469,6 +479,15 @@ msgstr ""
msgid "Search:"
msgstr ""
msgid ""
"Welcome to Joplin!\n"
"\n"
"Type `:help shortcuts` for the list of keyboard shortcuts, or just `:help` "
"for usage information.\n"
"\n"
"For example, to create a notebook press `mb`; to create a note press `mn`."
msgstr ""
msgid "File"
msgstr ""
@@ -508,6 +527,9 @@ msgstr ""
msgid "Tools"
msgstr ""
msgid "Synchronisation status"
msgstr ""
msgid "Options"
msgstr ""
@@ -530,6 +552,74 @@ msgstr ""
msgid "Cancel"
msgstr ""
#, javascript-format
msgid "Notes and settings are stored in: %s"
msgstr ""
msgid "Save"
msgstr ""
msgid ""
"Disabling encryption means *all* your notes and attachments are going to be "
"re-synchronised and sent unencrypted to the sync target. Do you wish to "
"continue?"
msgstr ""
msgid ""
"Enabling encryption means *all* your notes and attachments are going to be "
"re-synchronised and sent encrypted to the sync target. Do not lose the "
"password as, for security purposes, this will be the *only* way to decrypt "
"the data! To enable encryption, please enter your password below."
msgstr ""
msgid "Disable encryption"
msgstr ""
msgid "Enable encryption"
msgstr ""
msgid "Master Keys"
msgstr ""
msgid "Active"
msgstr ""
msgid "ID"
msgstr ""
msgid "Source"
msgstr ""
msgid "Created"
msgstr ""
msgid "Updated"
msgstr ""
msgid "Password"
msgstr ""
msgid "Password OK"
msgstr ""
msgid ""
"Note: Only one master key is going to be used for encryption (the one marked "
"as \"active\"). Any of the keys might be used for decryption, depending on "
"how the notes or notebooks were originally encrypted."
msgstr ""
msgid "Status"
msgstr ""
msgid "Encryption is:"
msgstr ""
msgid "Enabled"
msgstr ""
msgid "Disabled"
msgstr ""
msgid "Back"
msgstr ""
@@ -568,6 +658,18 @@ msgstr ""
msgid "Layout"
msgstr ""
msgid "Some items cannot be synchronised."
msgstr ""
msgid "View them now"
msgstr ""
msgid "Some items cannot be decrypted."
msgstr ""
msgid "Set the password"
msgstr ""
msgid "Add or remove tags"
msgstr ""
@@ -583,6 +685,10 @@ msgstr ""
msgid "No notes in here. Create one by clicking on \"New note\"."
msgstr ""
msgid ""
"There is currently no notebook. Create one by clicking on \"New notebook\"."
msgstr ""
#, javascript-format
msgid "Unsupported link or message: %s"
msgstr ""
@@ -605,7 +711,10 @@ msgstr ""
msgid "Import"
msgstr ""
msgid "Delete notebook?"
msgid "Synchronisation Status"
msgstr ""
msgid "Encryption Options"
msgstr ""
msgid "Remove this tag from all the notes?"
@@ -629,6 +738,9 @@ msgstr ""
msgid "Searches"
msgstr ""
msgid "Please select where the sync status should be exported to"
msgstr ""
#, javascript-format
msgid "Usage: %s"
msgstr ""
@@ -736,14 +848,6 @@ msgstr ""
msgid "Cannot move note to \"%s\" notebook"
msgstr ""
msgid "File system synchronisation target directory"
msgstr ""
msgid ""
"The path to synchronise with when file system synchronisation is enabled. "
"See `sync.target`."
msgstr ""
msgid "Text editor"
msgstr ""
@@ -779,9 +883,6 @@ msgstr ""
msgid "Synchronisation interval"
msgstr ""
msgid "Disabled"
msgstr ""
#, javascript-format
msgid "%d minutes"
msgstr ""
@@ -808,10 +909,25 @@ msgid ""
"`sync.2.path` to specify the target directory."
msgstr ""
msgid "Directory to synchronise with (absolute path)"
msgstr ""
msgid ""
"The path to synchronise with when file system synchronisation is enabled. "
"See `sync.target`."
msgstr ""
#, javascript-format
msgid "Invalid option value: \"%s\". Possible values are: %s."
msgstr ""
msgid "Items that cannot be synchronised"
msgstr ""
#, javascript-format
msgid "\"%s\": \"%s\""
msgstr ""
msgid "Sync status (synced items / total items)"
msgstr ""
@@ -854,9 +970,6 @@ msgstr ""
msgid "Log"
msgstr ""
msgid "Status"
msgstr ""
msgid "Export Debug Report"
msgstr ""

1137
CliClient/locales/pt_BR.po Normal file

File diff suppressed because it is too large Load Diff

1134
CliClient/locales/ru_RU.po Normal file

File diff suppressed because it is too large Load Diff

1093
CliClient/locales/zh_CN.po Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "joplin",
"version": "0.10.77",
"version": "0.10.84",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -460,16 +460,6 @@
"assert-plus": "1.0.0"
}
},
"gettext-parser": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/gettext-parser/-/gettext-parser-1.3.0.tgz",
"integrity": "sha512-iloxjcw+uTPnQ8DrGICWtqkHNgk3mAiDI77pLmXQCnhM+BxFQXstzTA4zj3EpIYMysRQnnNzHyHzBUEazz80Sw==",
"dev": true,
"requires": {
"encoding": "0.1.12",
"safe-buffer": "5.1.1"
}
},
"glob": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
@@ -797,12 +787,6 @@
"highlight.js": "9.12.0"
}
},
"marked": {
"version": "0.3.6",
"resolved": "https://registry.npmjs.org/marked/-/marked-0.3.6.tgz",
"integrity": "sha1-ssbGGPzOzk74bE/Gy4p8v1rtqNc=",
"dev": true
},
"md5": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/md5/-/md5-2.2.1.tgz",
@@ -884,12 +868,6 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
},
"mustache": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/mustache/-/mustache-2.3.0.tgz",
"integrity": "sha1-QCj3d4sXcIpImTCm5SrDvKDaQdA=",
"dev": true
},
"nan": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.7.0.tgz",

View File

@@ -18,7 +18,7 @@
],
"owner": "Laurent Cozic"
},
"version": "0.10.77",
"version": "0.10.84",
"bin": {
"joplin": "./main.js"
},
@@ -60,10 +60,7 @@
"yargs-parser": "^7.0.0"
},
"devDependencies": {
"gettext-parser": "^1.2.2",
"jasmine": "^2.6.0",
"marked": "^0.3.6",
"mustache": "^2.3.0"
"jasmine": "^2.6.0"
},
"scripts": {
"test": "jasmine"

View File

@@ -7,4 +7,4 @@ rsync -a "$ROOT_DIR/../ReactNativeClient/lib/" "$BUILD_DIR/lib/"
rsync -a "$ROOT_DIR/build/locales/" "$BUILD_DIR/locales/"
mkdir -p "$BUILD_DIR/data"
npm test tests-build/synchronizer.js
(cd "$ROOT_DIR" && npm test tests-build/synchronizer.js)

View File

@@ -15,7 +15,7 @@ process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
});
jasmine.DEFAULT_TIMEOUT_INTERVAL = 9000; // The first test is slow because the database needs to be built
jasmine.DEFAULT_TIMEOUT_INTERVAL = 15000; // The first test is slow because the database needs to be built
async function allItems() {
let folders = await Folder.all();
@@ -100,6 +100,7 @@ describe('Synchronizer', function() {
await synchronizer().start();
let all = await allItems();
await localItemsSameAsRemote(all, expect);
done();
@@ -627,5 +628,33 @@ describe('Synchronizer', function() {
done();
});
it('items should skip items that cannot be synced', async (done) => {
let folder1 = await Folder.save({ title: "folder1" });
let note1 = await Note.save({ title: "un", is_todo: 1, parent_id: folder1.id });
const noteId = note1.id;
await synchronizer().start();
let disabledItems = await BaseItem.syncDisabledItems();
expect(disabledItems.length).toBe(0);
await Note.save({ id: noteId, title: "un mod", });
synchronizer().debugFlags_ = ['cannotSync'];
await synchronizer().start();
synchronizer().debugFlags_ = [];
await synchronizer().start(); // Another sync to check that this item is now excluded from sync
await switchClient(2);
await synchronizer().start();
let notes = await Note.all();
expect(notes.length).toBe(1);
expect(notes[0].title).toBe('un');
await switchClient(1);
disabledItems = await BaseItem.syncDisabledItems();
expect(disabledItems.length).toBe(1);
done();
});
});

View File

@@ -43,8 +43,9 @@ const syncDir = __dirname + '/../tests/sync';
const sleepTime = syncTargetId_ == SyncTargetRegistry.nameToId('filesystem') ? 1001 : 400;
const logger = new Logger();
logger.addTarget('console');
logger.addTarget('file', { path: logDir + '/log.txt' });
logger.setLevel(Logger.LEVEL_DEBUG);
logger.setLevel(Logger.LEVEL_WARN);
BaseItem.loadClass('Note', Note);
BaseItem.loadClass('Folder', Folder);

View File

@@ -1,5 +1,6 @@
const { _ } = require('lib/locale.js');
const { BrowserWindow } = require('electron');
const { shim } = require('lib/shim');
const url = require('url')
const path = require('path')
const urlUtils = require('lib/urlUtils.js');
@@ -38,12 +39,18 @@ class ElectronAppWrapper {
defaultHeight: 600,
});
this.win_ = new BrowserWindow({
const windowOptions = {
'x': windowState.x,
'y': windowState.y,
'width': windowState.width,
'height': windowState.height
})
'height': windowState.height,
};
// Linux icon workaround for bug https://github.com/electron-userland/electron-builder/issues/2098
// Fix: https://github.com/electron-userland/electron-builder/issues/2269
if (shim.isLinux()) windowOptions.icon = __dirname + '/build/icons/128x128.png';
this.win_ = new BrowserWindow(windowOptions)
this.win_.loadURL(url.format({
pathname: path.join(__dirname, 'index.html'),

View File

@@ -259,6 +259,14 @@ class Application extends BaseApplication {
}, {
label: _('Tools'),
submenu: [{
label: _('Synchronisation status'),
click: () => {
this.dispatch({
type: 'NAV_GO',
routeName: 'Status',
});
}
},{
label: _('Options'),
click: () => {
this.dispatch({

View File

@@ -39,10 +39,22 @@ class Bridge {
return this.window().setSize(width, height);
}
showSaveDialog(options) {
const {dialog} = require('electron');
if (!options) options = {};
if (!('defaultPath' in options) && this.lastSelectedPath_) options.defaultPath = this.lastSelectedPath_;
const filePath = dialog.showSaveDialog(options);
if (filePath) {
this.lastSelectedPath_ = filePath;
}
return filePath;
}
showOpenDialog(options) {
const {dialog} = require('electron');
if (!options) options = {};
if (!('defaultPath' in options) && this.lastSelectedPath_) options.defaultPath = this.lastSelectedPath_;
if (!('createDirectory' in options)) options.createDirectory = true;
const filePaths = dialog.showOpenDialog(options);
if (filePaths && filePaths.length) {
this.lastSelectedPath_ = dirname(filePaths[0]);
@@ -71,6 +83,15 @@ class Bridge {
return result === 0;
}
showInfoMessageBox(message) {
const result = this.showMessageBox({
type: 'info',
message: message,
buttons: [_('OK')],
});
return result === 0;
}
get Menu() {
return require('electron').Menu;
}

View File

@@ -21,7 +21,7 @@ fs.readdirSync(guiPath).forEach((filename) => {
if (ext !== 'jsx') return;
p.pop();
const basePath = p.join('/');
const basePath = p.join('.');
const jsPath = basePath + '.min.js';

View File

@@ -5,10 +5,40 @@ const { Setting } = require('lib/models/setting.js');
const { bridge } = require('electron').remote.require('./bridge');
const { Header } = require('./Header.min.js');
const { themeStyle } = require('../theme.js');
const pathUtils = require('lib/path-utils.js');
const { _ } = require('lib/locale.js');
class ConfigScreenComponent extends React.Component {
constructor() {
super();
this.state = {
settings: {},
};
}
componentWillMount() {
this.setState({ settings: this.props.settings });
}
keyValueToArray(kv) {
let output = [];
for (let k in kv) {
if (!kv.hasOwnProperty(k)) continue;
output.push({
key: k,
label: kv[k],
});
}
output.sort((a, b) => {
return a.label.toLowerCase() < b.label.toLowerCase() ? -1 : +1;
});
return output;
}
settingToComponent(key, value) {
const theme = themeStyle(this.props.theme);
@@ -28,7 +58,9 @@ class ConfigScreenComponent extends React.Component {
};
const updateSettingValue = (key, value) => {
Setting.setValue(key, value);
const settings = Object.assign({}, this.state.settings);
settings[key] = value;
this.setState({ settings: settings });
}
// Component key needs to be key+value otherwise it doesn't update when the settings change.
@@ -38,13 +70,14 @@ class ConfigScreenComponent extends React.Component {
if (md.isEnum) {
let items = [];
const settingOptions = md.options();
for (let k in settingOptions) {
if (!settingOptions.hasOwnProperty(k)) continue;
items.push(<option value={k.toString()} key={k}>{settingOptions[k]}</option>);
let array = this.keyValueToArray(settingOptions);
for (let i = 0; i < array.length; i++) {
const e = array[i];
items.push(<option value={e.key.toString()} key={e.key}>{settingOptions[e.key]}</option>);
}
return (
<div key={key+value} style={rowStyle}>
<div key={key} style={rowStyle}>
<div style={labelStyle}><label>{md.label()}</label></div>
<select value={value} style={controlStyle} onChange={(event) => { updateSettingValue(key, event.target.value) }}>
{items}
@@ -52,22 +85,53 @@ class ConfigScreenComponent extends React.Component {
</div>
);
} else if (md.type === Setting.TYPE_BOOL) {
const onCheckboxClick = (event) => {
updateSettingValue(key, !value)
}
return (
<div key={key+value} style={rowStyle}>
<div key={key} style={rowStyle}>
<div style={controlStyle}>
<label><input type="checkbox" defaultChecked={!!value} onChange={(event) => { updateSettingValue(key, !!event.target.checked) }}/><span style={labelStyle}> {md.label()}</span></label>
<input id={'setting_checkbox_' + key} type="checkbox" checked={!!value} onChange={(event) => { onCheckboxClick(event) }}/><label onClick={(event) => { onCheckboxClick(event) }} style={labelStyle} htmlFor={'setting_checkbox_' + key}>{md.label()}</label>
</div>
</div>
);
} else if (md.type === Setting.TYPE_STRING) {
const onTextChange = (event) => {
const settings = Object.assign({}, this.state.settings);
settings[key] = event.target.value;
this.setState({ settings: settings });
}
return (
<div key={key} style={rowStyle}>
<div style={labelStyle}><label>{md.label()}</label></div>
<input type="text" style={controlStyle} value={this.state.settings[key]} onChange={(event) => {onTextChange(event)}} />
</div>
);
} else {
console.warn('Type not implemented: ' + key);
}
return output;
}
onSaveClick() {
for (let n in this.state.settings) {
if (!this.state.settings.hasOwnProperty(n)) continue;
Setting.setValue(n, this.state.settings[n]);
}
this.props.dispatch({ type: 'NAV_BACK' });
}
onCancelClick() {
this.props.dispatch({ type: 'NAV_BACK' });
}
render() {
const theme = themeStyle(this.props.theme);
const style = this.props.style;
const settings = this.props.settings;
const settings = this.state.settings;
const headerStyle = {
width: style.width,
@@ -77,15 +141,21 @@ class ConfigScreenComponent extends React.Component {
padding: 10,
};
const buttonStyle = {
display: this.state.settings === this.props.settings ? 'none' : 'inline-block',
marginRight: 10,
}
let settingComps = [];
let keys = Setting.keys(true, 'desktop');
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
if (key === 'sync.target') continue;
if (!(key in settings)) {
console.warn('Missing setting: ' + key);
continue;
}
const md = Setting.settingMetadata(key);
if (md.show && !md.show(settings)) continue;
const comp = this.settingToComponent(key, settings[key]);
if (!comp) continue;
settingComps.push(comp);
@@ -95,7 +165,12 @@ class ConfigScreenComponent extends React.Component {
<div style={style}>
<Header style={headerStyle} />
<div style={containerStyle}>
<div style={Object.assign({}, theme.textStyle, {marginBottom: 20})}>
{_('Notes and settings are stored in: %s', pathUtils.toSystemSlashes(Setting.value('profileDir'), process.platform))}
</div>
{ settingComps }
<button onClick={() => {this.onSaveClick()}} style={buttonStyle}>{_('Save')}</button>
<button onClick={() => {this.onCancelClick()}} style={buttonStyle}>{_('Cancel')}</button>
</div>
</div>
);

View File

@@ -41,19 +41,24 @@ class ImportScreenComponent extends React.Component {
const messages = this.state.messages.slice();
let found = false;
for (let i = 0; i < messages.length; i++) {
if (messages[i].key === key) {
messages[i].text = text;
found = true;
break;
}
}
if (!found) messages.push({ key: key, text: text });
messages.push({ key: key, text: text });
this.setState({ messages: messages });
}
uniqueMessages() {
let output = [];
const messages = this.state.messages.slice();
let foundKeys = [];
for (let i = messages.length - 1; i >= 0; i--) {
const msg = messages[i];
if (foundKeys.indexOf(msg.key) >= 0) continue;
foundKeys.push(msg.key);
output.unshift(msg);
}
return output;
}
async doImport() {
const filePath = this.props.filePath;
const folderTitle = await Folder.findUniqueFolderTitle(filename(filePath));
@@ -77,10 +82,9 @@ class ImportScreenComponent extends React.Component {
this.addMessage('progress', lastProgress);
},
onError: (error) => {
const messages = this.state.messages.slice();
let s = error.trace ? error.trace : error.toString();
messages.push({ key: 'error_' + (progressCount++), text: s });
this.addMessage('error_' + (progressCount++), lastProgress);
// Don't display the error directly because most of the time it doesn't matter
// (eg. for weird broken HTML, but the note is still imported)
console.warn('When importing ENEX file', error);
},
}
@@ -95,7 +99,7 @@ class ImportScreenComponent extends React.Component {
render() {
const theme = themeStyle(this.props.theme);
const style = this.props.style;
const messages = this.state.messages;
const messages = this.uniqueMessages();
const messagesStyle = {
padding: 10,

View File

@@ -229,8 +229,8 @@ class MainScreenComponent extends React.Component {
}
}
styles(themeId, width, height) {
const styleKey = themeId + '_' + width + '_' + height;
styles(themeId, width, height, messageBoxVisible) {
const styleKey = themeId + '_' + width + '_' + height + '_' + messageBoxVisible;
if (styleKey === this.styleKey_) return this.styles_;
const theme = themeStyle(themeId);
@@ -239,12 +239,21 @@ class MainScreenComponent extends React.Component {
this.styles_ = {};
const rowHeight = height - theme.headerHeight;
this.styles_.header = {
width: width,
};
this.styles_.messageBox = {
width: width,
height: 30,
display: 'flex',
alignItems: 'center',
paddingLeft: 10,
backgroundColor: theme.warningBackgroundColor,
}
const rowHeight = height - theme.headerHeight - (messageBoxVisible ? this.styles_.messageBox.height : 0);
this.styles_.sideBar = {
width: Math.floor(layoutUtils.size(width * .2, 150, 300)),
height: rowHeight,
@@ -279,8 +288,10 @@ class MainScreenComponent extends React.Component {
const promptOptions = this.state.promptOptions;
const folders = this.props.folders;
const notes = this.props.notes;
const messageBoxVisible = this.props.hasDisabledSyncItems;
const styles = this.styles(this.props.theme, style.width, style.height);
const styles = this.styles(this.props.theme, style.width, style.height, messageBoxVisible);
const theme = themeStyle(this.props.theme);
const headerButtons = [];
@@ -325,6 +336,21 @@ class MainScreenComponent extends React.Component {
}
}
const onViewDisabledItemsClick = () => {
this.props.dispatch({
type: 'NAV_GO',
routeName: 'Status',
});
}
const messageComp = messageBoxVisible ? (
<div style={styles.messageBox}>
<span style={theme.textStyle}>
{_('Some items cannot be synchronised.')} <a href="#" onClick={() => { onViewDisabledItemsClick() }}>{_('View them now')}</a>
</span>
</div>
) : null;
return (
<div style={style}>
<PromptDialog
@@ -339,6 +365,7 @@ class MainScreenComponent extends React.Component {
buttons={promptOptions && ('buttons' in promptOptions) ? promptOptions.buttons : null}
inputType={promptOptions && ('inputType' in promptOptions) ? promptOptions.inputType : null} />
<Header style={styles.header} showBackButton={false} buttons={headerButtons} />
{messageComp}
<SideBar style={styles.sideBar} />
<NoteList style={styles.noteList} />
<NoteText style={styles.noteText} visiblePanes={this.props.noteVisiblePanes} />
@@ -355,6 +382,7 @@ const mapStateToProps = (state) => {
noteVisiblePanes: state.noteVisiblePanes,
folders: state.folders,
notes: state.notes,
hasDisabledSyncItems: state.hasDisabledSyncItems,
};
};

View File

@@ -174,7 +174,7 @@ class NoteListComponent extends React.Component {
}, style);
emptyDivStyle.width = emptyDivStyle.width - padding * 2;
emptyDivStyle.height = emptyDivStyle.height - padding * 2;
return <div style={emptyDivStyle}>{_('No notes in here. Create one by clicking on "New note".')}</div>
return <div style={emptyDivStyle}>{ this.props.folders.length ? _('No notes in here. Create one by clicking on "New note".') : _('There is currently no notebook. Create one by clicking on "New notebook".')}</div>
}
return (
@@ -193,6 +193,7 @@ class NoteListComponent extends React.Component {
const mapStateToProps = (state) => {
return {
notes: state.notes,
folders: state.folders,
selectedNoteIds: state.selectedNoteIds,
theme: state.settings.theme,
// uncompletedTodosOnTop: state.settings.uncompletedTodosOnTop,

View File

@@ -36,7 +36,7 @@ class NoteTextComponent extends React.Component {
isLoading: true,
webviewReady: false,
scrollHeight: null,
editorScrollTop: 0,
editorScrollTop: 0
};
this.lastLoadedNoteId_ = null;
@@ -167,6 +167,12 @@ class NoteTextComponent extends React.Component {
async componentWillReceiveProps(nextProps) {
if ('noteId' in nextProps && nextProps.noteId !== this.props.noteId) {
await this.reloadNote(nextProps);
if(this.editor_){
const session = this.editor_.editor.getSession();
const undoManager = session.getUndoManager();
undoManager.reset();
session.setUndoManager(undoManager);
}
}
if ('syncStarted' in nextProps && !nextProps.syncStarted && !this.isModified()) {
@@ -334,6 +340,7 @@ class NoteTextComponent extends React.Component {
this.scheduleSave();
}
async commandAttachFile() {
const noteId = this.props.noteId;
if (!noteId) return;
@@ -549,7 +556,6 @@ class NoteTextComponent extends React.Component {
delete editorRootStyle.width;
delete editorRootStyle.height;
delete editorRootStyle.fontSize;
const editor = <AceEditor
value={body}
mode="markdown"

View File

@@ -8,6 +8,7 @@ const { Setting } = require('lib/models/setting.js');
const { MainScreen } = require('./MainScreen.min.js');
const { OneDriveLoginScreen } = require('./OneDriveLoginScreen.min.js');
const { StatusScreen } = require('./StatusScreen.min.js');
const { ImportScreen } = require('./ImportScreen.min.js');
const { ConfigScreen } = require('./ConfigScreen.min.js');
const { Navigator } = require('./Navigator.min.js');
@@ -75,6 +76,7 @@ class RootComponent extends React.Component {
OneDriveLogin: { screen: OneDriveLoginScreen, title: () => _('OneDrive Login') },
Import: { screen: ImportScreen, title: () => _('Import') },
Config: { screen: ConfigScreen, title: () => _('Options') },
Status: { screen: StatusScreen, title: () => _('Synchronisation Status') },
};
return (

View File

@@ -98,7 +98,7 @@ class SideBarComponent extends React.Component {
let deleteMessage = '';
if (itemType === BaseModel.TYPE_FOLDER) {
deleteMessage = _('Delete notebook?');
deleteMessage = _('Delete notebook? All notes within this notebook will also be deleted.');
} else if (itemType === BaseModel.TYPE_TAG) {
deleteMessage = _('Remove this tag from all the notes?');
} else if (itemType === BaseModel.TYPE_SEARCH) {

View File

@@ -0,0 +1,135 @@
const React = require('react');
const { connect } = require('react-redux');
const { reg } = require('lib/registry.js');
const { Setting } = require('lib/models/setting.js');
const { bridge } = require('electron').remote.require('./bridge');
const { Header } = require('./Header.min.js');
const { themeStyle } = require('../theme.js');
const { _ } = require('lib/locale.js');
const { ReportService } = require('lib/services/report.js');
const fs = require('fs-extra');
class StatusScreenComponent extends React.Component {
constructor() {
super();
this.state = {
report: [],
};
}
componentWillMount() {
this.resfreshScreen();
}
async resfreshScreen() {
const service = new ReportService();
const report = await service.status(Setting.value('sync.target'));
this.setState({ report: report });
}
async exportDebugReportClick() {
const filename = 'syncReport-' + (new Date()).getTime() + '.csv';
const filePath = bridge().showSaveDialog({
title: _('Please select where the sync status should be exported to'),
defaultPath: filename,
});
if (!filePath) return;
const service = new ReportService();
const csv = await service.basicItemList({ format: 'csv' });
await fs.writeFileSync(filePath, csv);
}
render() {
const theme = themeStyle(this.props.theme);
const style = this.props.style;
const headerStyle = {
width: style.width,
};
const containerPadding = 10;
const containerStyle = {
padding: containerPadding,
overflowY: 'auto',
height: style.height - theme.headerHeight - containerPadding * 2,
};
function renderSectionTitleHtml(key, title) {
return <h2 key={'section_' + key} style={theme.h2Style}>{title}</h2>
}
function renderSectionHtml(key, section) {
let itemsHtml = [];
itemsHtml.push(renderSectionTitleHtml(section.title, section.title));
for (let n in section.body) {
if (!section.body.hasOwnProperty(n)) continue;
itemsHtml.push(<div style={theme.textStyle} key={'item_' + n}>{section.body[n]}</div>);
}
return (
<div key={key}>
{itemsHtml}
</div>
);
}
function renderBodyHtml(report) {
let output = [];
let baseStyle = {
paddingLeft: 6,
paddingRight: 6,
paddingTop: 2,
paddingBottom: 2,
flex: 0,
color: theme.color,
fontSize: theme.fontSize,
};
let sectionsHtml = [];
for (let i = 0; i < report.length; i++) {
let section = report[i];
if (!section.body.length) continue;
sectionsHtml.push(renderSectionHtml(i, section));
}
return (
<div>
{sectionsHtml}
</div>
);
}
let body = renderBodyHtml(this.state.report);
return (
<div style={style}>
<Header style={headerStyle} />
<div style={containerStyle}>
<a style={theme.textStyle} onClick={() => this.exportDebugReportClick()}href="#">Export debug report</a>
{body}
</div>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
theme: state.settings.theme,
settings: state.settings,
locale: state.settings.locale,
};
};
const StatusScreen = connect(mapStateToProps)(StatusScreenComponent);
module.exports = { StatusScreen };

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

@@ -1,5 +1,13 @@
var locales = {};
locales['en_GB'] = require('./en_GB.json');
locales['de_DE'] = require('./de_DE.json');
locales['es_CR'] = require('./es_CR.json');
locales['es_ES'] = require('./es_ES.json');
locales['fr_FR'] = require('./fr_FR.json');
locales['hr_HR'] = require('./hr_HR.json');
locales['it_IT'] = require('./it_IT.json');
locales['ja_JP'] = require('./ja_JP.json');
locales['pt_BR'] = require('./pt_BR.json');
locales['ru_RU'] = require('./ru_RU.json');
locales['zh_CN'] = require('./zh_CN.json');
module.exports = { locales: locales };

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

@@ -1,6 +1,6 @@
{
"name": "Joplin",
"version": "0.10.35",
"version": "0.10.40",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "Joplin",
"version": "0.10.35",
"version": "0.10.40",
"description": "Joplin for Desktop",
"main": "main.js",
"scripts": {
@@ -25,6 +25,10 @@
"win": {
"icon": "../../Assets/Joplin.ico"
},
"nsis": {
"oneClick": false,
"allowToChangeInstallationDirectory": true
},
"mac": {
"icon": "../../Assets/macOs.icns",
"asar": false

View File

@@ -9,6 +9,19 @@ body, textarea {
overflow: hidden;
}
table {
border-collapse: collapse;
}
table th {
text-align: left;
}
table td, table th {
padding: .5em;
border: 1px solid #ccc;
}
/* By default, the Ice Editor displays invalid characters, such as non-breaking spaces
as red boxes, but since those are actually valid characters and common in imported
Evernote data, we hide them here. */

View File

@@ -25,6 +25,8 @@ const globalStyle = {
selectedColor2: "#5A4D70",
colorError2: "#ff6c6c",
warningBackgroundColor: "#FFD08D",
headerHeight: 35,
headerButtonHPadding: 6,
@@ -69,6 +71,9 @@ globalStyle.textStyle2 = Object.assign({}, globalStyle.textStyle, {
color: globalStyle.color2,
});
globalStyle.h2Style = Object.assign({}, globalStyle.textStyle);
globalStyle.h2Style.fontSize *= 1.3;
let themeCache_ = {};
function themeStyle(theme) {

View File

@@ -14,4 +14,4 @@ echo "Create a draft release at: https://github.com/laurent22/joplin/releases/ta
echo ""
echo "Then run:"
echo ""
echo "node $APP_DIR/update-readme-download.js"
echo "node $APP_DIR/update-readme-download.js && git add -A && git commit -m 'Update website' && git push"

View File

@@ -3,4 +3,4 @@ ROOT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
cd "$ROOT_DIR"
./build.sh || exit 1
cd "$ROOT_DIR/app"
./node_modules/.bin/electron . --env dev --log-level debug --open-dev-tools "$@"
./node_modules/.bin/electron . --env dev --log-level warn --open-dev-tools "$@"

View File

@@ -18,9 +18,9 @@ Three types of applications are available: for the **desktop** (Windows, macOS a
Operating System | Download
-----------------|--------
Windows | <a href='https://github.com/laurent22/joplin/releases/download/v0.10.35/Joplin-Setup-0.10.35.exe'><img alt='Get it on Windows' height="40px" src='https://raw.githubusercontent.com/laurent22/joplin/master/docs/images/BadgeWindows.png'/></a>
macOS | <a href='https://github.com/laurent22/joplin/releases/download/v0.10.35/Joplin-0.10.35.dmg'><img alt='Get it on macOS' height="40px" src='https://raw.githubusercontent.com/laurent22/joplin/master/docs/images/BadgeMacOS.png'/></a>
Linux | <a href='https://github.com/laurent22/joplin/releases/download/v0.10.35/Joplin-0.10.35-x86_64.AppImage'><img alt='Get it on macOS' height="40px" src='https://raw.githubusercontent.com/laurent22/joplin/master/docs/images/BadgeLinux.png'/></a>
Windows | <a href='https://github.com/laurent22/joplin/releases/download/v0.10.39/Joplin-Setup-0.10.39.exe'><img alt='Get it on Windows' height="40px" src='https://raw.githubusercontent.com/laurent22/joplin/master/docs/images/BadgeWindows.png'/></a>
macOS | <a href='https://github.com/laurent22/joplin/releases/download/v0.10.39/Joplin-0.10.39.dmg'><img alt='Get it on macOS' height="40px" src='https://raw.githubusercontent.com/laurent22/joplin/master/docs/images/BadgeMacOS.png'/></a>
Linux | <a href='https://github.com/laurent22/joplin/releases/download/v0.10.39/Joplin-0.10.39-x86_64.AppImage'><img alt='Get it on macOS' height="40px" src='https://raw.githubusercontent.com/laurent22/joplin/master/docs/images/BadgeLinux.png'/></a>
## Mobile applications
@@ -31,13 +31,13 @@ iOS | <a href='https://itunes.apple.com/us/app/joplin/id1315599797'
## Terminal application
**IMPORTANT: Node v8+ is required**
On macOS:
brew install joplin
brew install node joplin
On Linux or Windows (via [WSL](https://msdn.microsoft.com/en-us/commandline/wsl/faq?f=255&MSPPError=-2147217396)), type:
On Linux or Windows (via [WSL](https://msdn.microsoft.com/en-us/commandline/wsl/faq?f=255&MSPPError=-2147217396)):
**Important:** First, [install Node 8+](https://nodejs.org/en/download/package-manager/). Node 8 is LTS but not yet available everywhere so you might need to manually install it.
NPM_CONFIG_PREFIX=~/.joplin-bin npm install -g joplin
sudo ln -s ~/.joplin-bin/bin/joplin /usr/bin/joplin
@@ -108,7 +108,7 @@ If for any reason the notifications do not work, please [open an issue](https://
# Localisation
Joplin is currently available in English, French and Spanish. If you would like to contribute a translation, it is quite straightforward, please follow these steps:
Joplin is currently available in English, French, Spanish, German, Portuguese and Italian. If you would like to contribute a translation, it is quite straightforward, please follow these steps:
- [Download Poedit](https://poedit.net/), the translation editor, and install it.
- [Download the file to be translated](https://raw.githubusercontent.com/laurent22/joplin/master/CliClient/locales/joplin.pot).

20
README_debugging.md Normal file
View File

@@ -0,0 +1,20 @@
# How to enable debugging
It is possible to get the apps to display or log more information that might help debug various issues.
## Desktop application
- Add a file named "flags.txt" in the config directory (should be `~/.config/joplin` or `c:\Users\YOUR_NAME\.config\joplin`) with the following content: `--open-dev-tools --log-level debug`
- Restart the application
- The development tools should now be opened. Click the "Console" tab
- Now repeat the action that was causing problem. The console might output warnings or errors - please add them to the GitHub issue. Also open log.txt in the config folder and if there is any error or warning, please also add them to the issue.
## CLI application
- Start the app with `joplin --log-level debug`
- Check the log.txt as specified above for the desktop application and attach the log to the GitHub issue (or just the warnings/errors if any)
## Mobile application
- In the options, enable Advanced Option
- Open the log in the top right hand corner menu and post a screenshot of any error/warning.

View File

@@ -10,13 +10,13 @@ The notes can be [synchronised](#synchronisation) with various targets including
# Installation
**IMPORTANT: Node v8+ is required**
On macOS:
brew install joplin
brew install node joplin
On Linux or Windows (via [WSL](https://msdn.microsoft.com/en-us/commandline/wsl/faq?f=255&MSPPError=-2147217396)), type:
On Linux or Windows (via [WSL](https://msdn.microsoft.com/en-us/commandline/wsl/faq?f=255&MSPPError=-2147217396)):
**Important:** First, [install Node 8+](https://nodejs.org/en/download/package-manager/). Node 8 is LTS but not yet available everywhere so you might need to manually install it.
NPM_CONFIG_PREFIX=~/.joplin-bin npm install -g joplin
sudo ln -s ~/.joplin-bin/bin/joplin /usr/bin/joplin
@@ -79,7 +79,7 @@ Rename the currently selected notebook ($b) to "Something":
Attach a local file to the currently selected note ($n):
ren $n /home/laurent/pictures/Vacation12.jpg
attach $n /home/laurent/pictures/Vacation12.jpg
The configuration can also be changed from command-line mode. For example, to change the current editor to Sublime Text:
@@ -137,6 +137,25 @@ Since this is still an actual URL, the terminal will still make it clickable. An
In Markdown, links to resources are represented as a simple ID to the resource. In order to give access to these resources, they will be, like links, converted to local URLs. Clicking this link will then open a browser, which will handle the file - i.e. display the image, open the PDF file, etc.
# Shell mode
Commands can also be used directly from a shell. To view the list of available commands, type `joplin help all`. To reference a note, notebook or tag you can either use the ID (type `joplin ls -l` to view the ID) or by title.
For example, this will create a new note "My note" in the notebook "My notebook":
$ joplin mkbook "My notebook"
$ joplin use "My notebook"
$ joplin mknote "My note"
To view the newly created note:
$ joplin ls -l
fe889 07/12/2017 17:57 My note
Give a new title to the note:
$ joplin set fe889 title "New title"
# Available shortcuts
There are two types of shortcuts: those that manipulate the user interface directly, such as `TAB` to move from one pane to another, and those that are simply shortcuts to actual commands. In a way similar to Vim, these shortcuts are generally a verb followed by an object. For example, typing `mn` ([m]ake [n]ote), is used to create a new note: it will switch the interface to command line mode and pre-fill it with `mknote ""` from where the title of the note can be entered. See below for the full list of shortcuts:

View File

@@ -90,8 +90,8 @@ android {
applicationId "net.cozic.joplin"
minSdkVersion 16
targetSdkVersion 22
versionCode 77
versionName "0.10.62"
versionCode 80
versionName "0.10.65"
ndk {
abiFilters "armeabi-v7a", "x86"
}

View File

@@ -179,6 +179,111 @@
remoteGlobalIDString = E23D7B471ACEFE2A00C59171;
remoteInfo = SQLite;
};
4D2AFF551FDA002000599716 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 5E91572D1DD0AC6500FF2AA8 /* RCTAnimation.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 2D2A28201D9B03D100D4039D;
remoteInfo = "RCTAnimation-tvOS";
};
4D2AFF591FDA002000599716 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = ADBDB91F1DFEBF0600ED6528 /* RCTBlob.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = ADD01A681E09402E00F6D226;
remoteInfo = "RCTBlob-tvOS";
};
4D2AFF5E1FDA002000599716 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 2D2A283A1D9B042B00D4039D;
remoteInfo = "RCTImage-tvOS";
};
4D2AFF631FDA002000599716 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 2D2A28471D9B043800D4039D;
remoteInfo = "RCTLinking-tvOS";
};
4D2AFF671FDA002000599716 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 00C302D31ABCB9D200DB3ED1 /* RCTNetwork.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 2D2A28541D9B044C00D4039D;
remoteInfo = "RCTNetwork-tvOS";
};
4D2AFF6D1FDA002000599716 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 2D2A28611D9B046600D4039D;
remoteInfo = "RCTSettings-tvOS";
};
4D2AFF711FDA002000599716 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 2D2A287B1D9B048500D4039D;
remoteInfo = "RCTText-tvOS";
};
4D2AFF781FDA002000599716 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 2D2A28881D9B049200D4039D;
remoteInfo = "RCTWebSocket-tvOS";
};
4D2AFF7A1FDA002000599716 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 3DBE0D0D1F3B181C0099AA32;
remoteInfo = "fishhook-tvOS";
};
4D2AFF881FDA002000599716 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 2D2A28131D9B038B00D4039D;
remoteInfo = "React-tvOS";
};
4D2AFF8A1FDA002000599716 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 3D3C06751DE3340C00C268FA;
remoteInfo = "yoga-tvOS";
};
4D2AFF8C1FDA002000599716 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 3D3CD9321DE5FBEE00167DC4;
remoteInfo = "cxxreact-tvOS";
};
4D2AFF8E1FDA002000599716 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 3D3CD9181DE5FBD800167DC4;
remoteInfo = "jschelpers-tvOS";
};
4D2AFF901FDA002000599716 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 3D383D3C1EBD27B6005632C8;
remoteInfo = "third-party-tvOS";
};
4D2AFF921FDA002000599716 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 3D383D621EBD27B9005632C8;
remoteInfo = "double-conversion-tvOS";
};
4D3A19261FBDDA9400457703 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */;
@@ -348,6 +453,7 @@
isa = PBXGroup;
children = (
00C302C01ABCB91800DB3ED1 /* libRCTImage.a */,
4D2AFF5F1FDA002000599716 /* libRCTImage-tvOS.a */,
);
name = Products;
sourceTree = "<group>";
@@ -356,6 +462,7 @@
isa = PBXGroup;
children = (
00C302DC1ABCB9D200DB3ED1 /* libRCTNetwork.a */,
4D2AFF681FDA002000599716 /* libRCTNetwork-tvOS.a */,
);
name = Products;
sourceTree = "<group>";
@@ -380,6 +487,7 @@
isa = PBXGroup;
children = (
139105C11AF99BAD00B5F7CC /* libRCTSettings.a */,
4D2AFF6E1FDA002000599716 /* libRCTSettings-tvOS.a */,
);
name = Products;
sourceTree = "<group>";
@@ -388,7 +496,9 @@
isa = PBXGroup;
children = (
139FDEF41B06529B00C62182 /* libRCTWebSocket.a */,
4D2AFF791FDA002000599716 /* libRCTWebSocket-tvOS.a */,
4D2A85AA1FBCE3AC0028537D /* libfishhook.a */,
4D2AFF7B1FDA002000599716 /* libfishhook-tvOS.a */,
);
name = Products;
sourceTree = "<group>";
@@ -412,11 +522,17 @@
isa = PBXGroup;
children = (
146834041AC3E56700842450 /* libReact.a */,
4D2AFF891FDA002000599716 /* libReact.a */,
3DAD3EA51DF850E9000B6D8A /* libyoga.a */,
4D2AFF8B1FDA002000599716 /* libyoga.a */,
3DAD3EA91DF850E9000B6D8A /* libcxxreact.a */,
4D2AFF8D1FDA002000599716 /* libcxxreact.a */,
3DAD3EAD1DF850E9000B6D8A /* libjschelpers.a */,
4D2AFF8F1FDA002000599716 /* libjschelpers.a */,
4D3A19271FBDDA9400457703 /* libthird-party.a */,
4D2AFF911FDA002000599716 /* libthird-party.a */,
4D3A192B1FBDDA9400457703 /* libdouble-conversion.a */,
4D2AFF931FDA002000599716 /* libdouble-conversion.a */,
);
name = Products;
sourceTree = "<group>";
@@ -505,6 +621,7 @@
isa = PBXGroup;
children = (
5E9157331DD0AC6500FF2AA8 /* libRCTAnimation.a */,
4D2AFF561FDA002000599716 /* libRCTAnimation.a */,
);
name = Products;
sourceTree = "<group>";
@@ -513,6 +630,7 @@
isa = PBXGroup;
children = (
78C398B91ACF4ADC00677621 /* libRCTLinking.a */,
4D2AFF641FDA002000599716 /* libRCTLinking-tvOS.a */,
);
name = Products;
sourceTree = "<group>";
@@ -566,6 +684,7 @@
isa = PBXGroup;
children = (
832341B51AAA6A8300B99B32 /* libRCTText.a */,
4D2AFF721FDA002000599716 /* libRCTText-tvOS.a */,
);
name = Products;
sourceTree = "<group>";
@@ -597,6 +716,7 @@
isa = PBXGroup;
children = (
ADBDB9271DFEBF0700ED6528 /* libRCTBlob.a */,
4D2AFF5A1FDA002000599716 /* libRCTBlob-tvOS.a */,
);
name = Products;
sourceTree = "<group>";
@@ -879,6 +999,111 @@
remoteRef = 4D2A85CC1FBCE3AD0028537D /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
4D2AFF561FDA002000599716 /* libRCTAnimation.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libRCTAnimation.a;
remoteRef = 4D2AFF551FDA002000599716 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
4D2AFF5A1FDA002000599716 /* libRCTBlob-tvOS.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = "libRCTBlob-tvOS.a";
remoteRef = 4D2AFF591FDA002000599716 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
4D2AFF5F1FDA002000599716 /* libRCTImage-tvOS.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = "libRCTImage-tvOS.a";
remoteRef = 4D2AFF5E1FDA002000599716 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
4D2AFF641FDA002000599716 /* libRCTLinking-tvOS.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = "libRCTLinking-tvOS.a";
remoteRef = 4D2AFF631FDA002000599716 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
4D2AFF681FDA002000599716 /* libRCTNetwork-tvOS.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = "libRCTNetwork-tvOS.a";
remoteRef = 4D2AFF671FDA002000599716 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
4D2AFF6E1FDA002000599716 /* libRCTSettings-tvOS.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = "libRCTSettings-tvOS.a";
remoteRef = 4D2AFF6D1FDA002000599716 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
4D2AFF721FDA002000599716 /* libRCTText-tvOS.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = "libRCTText-tvOS.a";
remoteRef = 4D2AFF711FDA002000599716 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
4D2AFF791FDA002000599716 /* libRCTWebSocket-tvOS.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = "libRCTWebSocket-tvOS.a";
remoteRef = 4D2AFF781FDA002000599716 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
4D2AFF7B1FDA002000599716 /* libfishhook-tvOS.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = "libfishhook-tvOS.a";
remoteRef = 4D2AFF7A1FDA002000599716 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
4D2AFF891FDA002000599716 /* libReact.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libReact.a;
remoteRef = 4D2AFF881FDA002000599716 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
4D2AFF8B1FDA002000599716 /* libyoga.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libyoga.a;
remoteRef = 4D2AFF8A1FDA002000599716 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
4D2AFF8D1FDA002000599716 /* libcxxreact.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libcxxreact.a;
remoteRef = 4D2AFF8C1FDA002000599716 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
4D2AFF8F1FDA002000599716 /* libjschelpers.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libjschelpers.a;
remoteRef = 4D2AFF8E1FDA002000599716 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
4D2AFF911FDA002000599716 /* libthird-party.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = "libthird-party.a";
remoteRef = 4D2AFF901FDA002000599716 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
4D2AFF931FDA002000599716 /* libdouble-conversion.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = "libdouble-conversion.a";
remoteRef = 4D2AFF921FDA002000599716 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
4D3A19271FBDDA9400457703 /* libthird-party.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;

View File

@@ -17,11 +17,11 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>0.10.3</string>
<string>0.10.6</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>3</string>
<string>6</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSAppTransportSecurity</key>

View File

@@ -66,10 +66,15 @@ class BaseApplication {
}
switchCurrentFolder(folder) {
this.dispatch({
type: 'FOLDER_SELECT',
id: folder ? folder.id : '',
});
if (!this.hasGui()) {
this.currentFolder_ = Object.assign({}, folder);
Setting.setValue('activeFolderId', folder ? folder.id : '');
} else {
this.dispatch({
type: 'FOLDER_SELECT',
id: folder ? folder.id : '',
});
}
}
// Handles the initial flags passed to main script and
@@ -227,6 +232,10 @@ class BaseApplication {
return false;
}
uiType() {
return this.hasGui() ? 'gui' : 'cli';
}
generalMiddlewareFn() {
const middleware = store => next => (action) => {
return this.generalMiddleware(store, next, action);
@@ -271,6 +280,11 @@ class BaseApplication {
}
}
// if (action.type === 'NOTE_DELETE') {
// // Update folders if a note is deleted in case the deleted note was a conflict
// await FoldersScreenUtils.refreshFolders();
// }
if (this.hasGui() && action.type == 'SETTING_UPDATE_ONE' && action.key == 'sync.interval' || action.type == 'SETTING_UPDATE_ALL') {
reg.setupRecurrentSync();
}

View File

@@ -100,7 +100,7 @@ class MdToHtml {
const href = this.getAttr_(attrs, 'src');
if (!Resource.isResourceUrl(href)) {
return '<span>' + href + '</span><img title="' + htmlentities(title) + '" src="' + href + '"/>';
return '<img title="' + htmlentities(title) + '" src="' + href + '"/>';
}
const resourceId = Resource.urlToId(href);

View File

@@ -62,11 +62,12 @@ class BaseModel {
return temp;
}
static fieldType(name) {
static fieldType(name, defaultValue = null) {
let fields = this.fields();
for (let i = 0; i < fields.length; i++) {
if (fields[i].name == name) return fields[i].type;
}
if (defaultValue !== null) return defaultValue;
throw new Error('Unknown field: ' + name);
}
@@ -201,11 +202,11 @@ class BaseModel {
let output = {};
let type = null;
for (let n in newModel) {
if (!newModel.hasOwnProperty(n)) continue;
if (n == 'type_') {
type = n;
type = newModel[n];
continue;
}
if (!newModel.hasOwnProperty(n)) continue;
if (!(n in oldModel) || newModel[n] !== oldModel[n]) {
output[n] = newModel[n];
}
@@ -214,6 +215,12 @@ class BaseModel {
return output;
}
static modelsAreSame(oldModel, newModel) {
const diff = this.diffObjects(oldModel, newModel);
delete diff.type_;
return !Object.getOwnPropertyNames(diff).length;
}
static saveQuery(o, options) {
let temp = {}
let fieldNames = this.fieldNames();
@@ -232,6 +239,9 @@ class BaseModel {
o.updated_time = timeNow;
}
// The purpose of user_updated_time is to allow the user to manually set the time of a note (in which case
// options.autoTimestamp will be `false`). However note that if the item is later changed, this timestamp
// will be set again to the current time.
if (options.autoTimestamp && this.hasField('user_updated_time')) {
o.user_updated_time = timeNow;
}
@@ -272,6 +282,18 @@ class BaseModel {
options = this.modOptions(options);
options.isNew = this.isNew(o, options);
// Diff saving is an optimisation which takes a new version of the item and an old one,
// do a diff and save only this diff. IMPORTANT: When using this make sure that both
// models have been normalised using ItemClass.filter()
const isDiffSaving = options && options.oldItem && !options.isNew;
if (isDiffSaving) {
const newObject = BaseModel.diffObjects(options.oldItem, o);
newObject.type_ = o.type_;
newObject.id = o.id;
o = newObject;
}
o = this.filter(o);
let queries = [];
@@ -292,6 +314,15 @@ class BaseModel {
if ('user_updated_time' in saveQuery.modObject) o.user_updated_time = saveQuery.modObject.user_updated_time;
if ('user_created_time' in saveQuery.modObject) o.user_created_time = saveQuery.modObject.user_created_time;
o = this.addModelMd(o);
if (isDiffSaving) {
for (let n in options.oldItem) {
if (!options.oldItem.hasOwnProperty(n)) continue;
if (n in o) continue;
o[n] = options.oldItem[n];
}
}
return this.filter(o);
}).catch((error) => {
Log.error('Cannot save model', error);
@@ -322,9 +353,18 @@ class BaseModel {
let output = Object.assign({}, model);
for (let n in output) {
if (!output.hasOwnProperty(n)) continue;
// The SQLite database doesn't have booleans so cast everything to int
if (output[n] === true) output[n] = 1;
if (output[n] === false) output[n] = 0;
if (output[n] === true) {
output[n] = 1;
} else if (output[n] === false) {
output[n] = 0;
} else {
const t = this.fieldType(n, Database.TYPE_UNKNOWN);
if (t === Database.TYPE_INT) {
output[n] = !n ? 0 : parseInt(output[n], 10);
}
}
}
return output;

View File

@@ -17,15 +17,13 @@ class Dropdown extends React.Component {
});
}
componentDidMount() {
updateHeaderCoordinates() {
// https://stackoverflow.com/questions/30096038/react-native-getting-the-position-of-an-element
setTimeout(() => {
this.headerRef_.measure((fx, fy, width, height, px, py) => {
this.setState({
headerSize: { x: px, y: py, width: width, height: height }
});
this.headerRef_.measure((fx, fy, width, height, px, py) => {
this.setState({
headerSize: { x: px, y: py, width: width, height: height }
});
}, 100);
});
}
render() {
@@ -107,7 +105,10 @@ class Dropdown extends React.Component {
return (
<View style={{flex: 1, flexDirection: 'column' }}>
<TouchableOpacity style={headerWrapperStyle} ref={(ref) => this.headerRef_ = ref} onPress={() => { this.setState({ listVisible: true }) }}>
<TouchableOpacity style={headerWrapperStyle} ref={(ref) => this.headerRef_ = ref} onPress={() => {
this.updateHeaderCoordinates();
this.setState({ listVisible: true });
}}>
<Text ellipsizeMode="tail" numberOfLines={1} style={headerStyle}>{headerLabel}</Text>
<Text style={headerArrowStyle}>{'▼'}</Text>
</TouchableOpacity>

View File

@@ -1,6 +1,6 @@
const React = require('react'); const Component = React.Component;
const { connect } = require('react-redux');
const { Platform, View, Text, Button, StyleSheet, TouchableOpacity, Image } = require('react-native');
const { Platform, View, Text, Button, StyleSheet, TouchableOpacity, Image, ScrollView, Dimensions } = require('react-native');
const Icon = require('react-native-vector-icons/Ionicons').default;
const { Log } = require('lib/log.js');
const { BackButtonService } = require('lib/services/back-button.js');
@@ -218,7 +218,7 @@ class ScreenHeaderComponent extends Component {
const itemListCsv = await service.basicItemList({ format: 'csv' });
const filePath = RNFS.ExternalDirectoryPath + '/syncReport-' + (new Date()).getTime() + '.txt';
const finalText = [logItemCsv, itemListCsv].join("\n--------------------------------------------------------------------------------");
const finalText = [logItemCsv, itemListCsv].join("\n================================================================================\n");
await RNFS.writeFile(filePath, finalText);
alert('Debug report exported to ' + filePath);
@@ -410,6 +410,7 @@ class ScreenHeaderComponent extends Component {
const backButtonComp = backButton(this.styles(), () => this.backButton_press(), !this.props.historyCanGoBack);
const searchButtonComp = this.props.noteSelectionEnabled ? null : searchButton(this.styles(), () => this.searchButton_press());
const deleteButtonComp = this.props.noteSelectionEnabled ? deleteButton(this.styles(), () => this.deleteButton_press()) : null;
const windowHeight = Dimensions.get('window').height - 50;
const menuComp = (
<Menu onSelect={(value) => this.menu_select(value)} style={this.styles().contextMenu}>
@@ -417,7 +418,9 @@ class ScreenHeaderComponent extends Component {
<Text style={this.styles().contextMenuTrigger}> &#8942;</Text>
</MenuTrigger>
<MenuOptions>
{ menuOptionComponents }
<ScrollView style={{ maxHeight: windowHeight }}>
{ menuOptionComponents }
</ScrollView>
</MenuOptions>
</Menu>
);

View File

@@ -69,7 +69,7 @@ class NotesScreenComponent extends BaseScreenComponent {
}
deleteFolder_onPress(folderId) {
dialogs.confirm(this, _('Delete notebook?')).then((ok) => {
dialogs.confirm(this, _('Delete notebook? All notes within this notebook will also be deleted.')).then((ok) => {
if (!ok) return;
Folder.delete(folderId).then(() => {

View File

@@ -29,9 +29,11 @@ shared.saveNoteButton_press = async function(comp) {
}
let isNew = !note.id;
let titleWasAutoAssigned = false;
if (isNew && !note.title) {
note.title = Note.defaultTitle(note);
titleWasAutoAssigned = true;
}
// Save only the properties that have changed
@@ -54,8 +56,11 @@ shared.saveNoteButton_press = async function(comp) {
// But we preserve the current title and body because
// the user might have changed them between the time
// saveNoteButton_press was called and the note was
// saved (it's done asynchronously)
note.title = stateNote.title;
// saved (it's done asynchronously).
//
// If the title was auto-assigned above, we don't restore
// it from the state because it will be empty there.
if (!titleWasAutoAssigned) note.title = stateNote.title;
note.body = stateNote.body;
}

View File

@@ -165,6 +165,16 @@ class Database {
throw new Error('Unknown enum type or value: ' + type + ', ' + s);
}
static enumName(type, id) {
if (type === 'fieldType') {
if (id === Database.TYPE_UNKNOWN) return 'unknown';
if (id === Database.TYPE_INT) return 'int';
if (id === Database.TYPE_TEXT) return 'text';
if (id === Database.TYPE_NUMERIC) return 'numeric';
throw new Error('Invalid type id: ' + id);
}
}
static formatValue(type, value) {
if (value === null || value === undefined) return null;
if (type == this.TYPE_INT) return Number(value);
@@ -308,6 +318,7 @@ class Database {
}
Database.TYPE_UNKNOWN = 0;
Database.TYPE_INT = 1;
Database.TYPE_TEXT = 2;
Database.TYPE_NUMERIC = 3;

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