Compare commits
98 Commits
android-v0
...
cli-v0.10.
Author | SHA1 | Date | |
---|---|---|---|
|
0db9c66317 | ||
|
b22211fada | ||
|
79efac2f71 | ||
|
9d7b7092f5 | ||
|
7e086a7730 | ||
|
3f810c71b0 | ||
|
6a75451539 | ||
|
70a33f8533 | ||
|
23722719f0 | ||
|
d499251206 | ||
|
ab959623aa | ||
|
20632ae1c1 | ||
|
3146273409 | ||
|
38c050b47e | ||
|
0bf5c9ebdd | ||
|
0926755635 | ||
|
75f8234db5 | ||
|
b8e85e1587 | ||
|
b6add857f9 | ||
|
4e41731c08 | ||
|
6ac3b8a8f9 | ||
|
4756238821 | ||
|
7ab2b11c9d | ||
|
1eb1c5914c | ||
|
5780951f7d | ||
|
67e790e393 | ||
|
0d472486ca | ||
|
39c73e1649 | ||
|
3bf9d01f0a | ||
|
89ef33f7ca | ||
|
f71fe9a1a6 | ||
|
671e538740 | ||
|
2d218904d0 | ||
|
c3fbcb8feb | ||
|
64031ac6cf | ||
|
caae7f7cf8 | ||
|
d56b247adf | ||
|
11bdfbde61 | ||
|
a59bf55c16 | ||
|
043be1916c | ||
|
42e34b5c3b | ||
|
931083b2e2 | ||
|
cda623a95c | ||
|
0f343bccda | ||
|
a513f6f3f0 | ||
|
3b4809714e | ||
|
238b5ab9b9 | ||
|
08d9e9b6aa | ||
|
b22900eb3a | ||
|
89fc2c4779 | ||
|
353b79f5e5 | ||
|
b929b46281 | ||
|
5c4a536dad | ||
|
91e337307c | ||
|
2855b68ed4 | ||
|
45ca6284f9 | ||
|
145ee13356 | ||
|
6f97747199 | ||
|
f3751e4ba6 | ||
|
5cd55cada6 | ||
|
bad4b2ecb8 | ||
|
7aafd63ff3 | ||
|
3227a13035 | ||
|
027f96d100 | ||
|
f242a3c215 | ||
|
ad6c347180 | ||
|
09b9df4228 | ||
|
4f0431da55 | ||
|
b873fdd029 | ||
|
c1ff820913 | ||
|
7008daf92a | ||
|
ed914c6907 | ||
|
37663bd110 | ||
|
f01c6aa8d1 | ||
|
8838017830 | ||
|
1d6fb8058f | ||
|
bb51729bea | ||
|
507e7e6014 | ||
|
f42908b11c | ||
|
03ec406627 | ||
|
c703521b6c | ||
|
cf97bf9a77 | ||
|
304b9a582f | ||
|
4d5c4b1743 | ||
|
4abe5d07c4 | ||
|
f6633e23f5 | ||
|
a6d6201ecb | ||
|
4314c392f6 | ||
|
73e81a54b4 | ||
|
ab8c66a361 | ||
|
4b55fefcb1 | ||
|
0eac8b25e1 | ||
|
aec556ff7d | ||
|
110dc29bd4 | ||
|
b1efea1bd9 | ||
|
8671467ed3 | ||
|
485ef1f2c2 | ||
|
b9194e94aa |
BIN
Assets/All.psd
Before Width: | Height: | Size: 79 KiB |
Before Width: | Height: | Size: 92 KiB |
Before Width: | Height: | Size: 92 KiB |
Before Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 30 KiB |
BIN
Assets/Icon.psd
Before Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 3.3 KiB |
@@ -1,2 +0,0 @@
|
||||
#!/bin/bash
|
||||
iconutil --convert icns macOs.iconset
|
@@ -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 |
@@ -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 |
@@ -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 |
28
BUILD.md
@@ -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.
|
@@ -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;
|
||||
|
@@ -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) {
|
||||
|
185
CliClient/app/autocompletion.js
Normal 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 };
|
@@ -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();
|
||||
|
||||
|
@@ -12,6 +12,10 @@ class Command extends BaseCommand {
|
||||
return _('Exits the application.');
|
||||
}
|
||||
|
||||
compatibleUis() {
|
||||
return ['gui'];
|
||||
}
|
||||
|
||||
async action(args) {
|
||||
await app().exit();
|
||||
}
|
||||
|
@@ -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('');
|
||||
|
@@ -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);
|
||||
|
@@ -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) {
|
||||
|
@@ -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_));
|
||||
|
@@ -18,8 +18,8 @@ class Command extends BaseCommand {
|
||||
return { data: autocompleteFolders };
|
||||
}
|
||||
|
||||
enabled() {
|
||||
return false;
|
||||
compatibleUis() {
|
||||
return ['cli'];
|
||||
}
|
||||
|
||||
async action(args) {
|
||||
|
@@ -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 : '';
|
||||
|
@@ -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;
|
||||
|
@@ -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
|
@@ -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"
|
@@ -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"
|
@@ -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
@@ -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 ""
|
||||
|
||||
|
1151
CliClient/locales/es_ES.po
Normal 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
1141
CliClient/locales/it_IT.po
Normal file
1134
CliClient/locales/ja_JP.po
Normal 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
1134
CliClient/locales/ru_RU.po
Normal file
1093
CliClient/locales/zh_CN.po
Normal file
24
CliClient/package-lock.json
generated
@@ -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",
|
||||
|
@@ -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"
|
||||
|
@@ -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)
|
@@ -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();
|
||||
});
|
||||
|
||||
});
|
@@ -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);
|
||||
|
@@ -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'),
|
||||
|
@@ -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({
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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';
|
||||
|
||||
|
@@ -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>
|
||||
);
|
||||
|
@@ -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,
|
||||
|
@@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
|
@@ -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,
|
||||
|
@@ -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"
|
||||
|
@@ -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 (
|
||||
|
@@ -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) {
|
||||
|
135
ElectronClient/app/gui/StatusScreen.jsx
Normal 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 };
|
1
ElectronClient/app/locales/de_DE.json
Normal file
1
ElectronClient/app/locales/es_ES.json
Normal file
1
ElectronClient/app/locales/hr_HR.json
Normal 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 };
|
1
ElectronClient/app/locales/it_IT.json
Normal file
1
ElectronClient/app/locales/ja_JP.json
Normal file
1
ElectronClient/app/locales/pt_BR.json
Normal file
1
ElectronClient/app/locales/ru_RU.json
Normal file
1
ElectronClient/app/locales/zh_CN.json
Normal file
2
ElectronClient/app/package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "Joplin",
|
||||
"version": "0.10.35",
|
||||
"version": "0.10.40",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
@@ -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
|
||||
|
@@ -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. */
|
||||
|
@@ -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) {
|
||||
|
@@ -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"
|
@@ -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 "$@"
|
16
README.md
@@ -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
@@ -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.
|
@@ -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:
|
||||
|
@@ -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"
|
||||
}
|
||||
|
@@ -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;
|
||||
|
@@ -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>
|
||||
|
@@ -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();
|
||||
}
|
||||
|
@@ -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);
|
||||
|
@@ -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;
|
||||
|
@@ -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>
|
||||
|
@@ -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}> ⋮</Text>
|
||||
</MenuTrigger>
|
||||
<MenuOptions>
|
||||
{ menuOptionComponents }
|
||||
<ScrollView style={{ maxHeight: windowHeight }}>
|
||||
{ menuOptionComponents }
|
||||
</ScrollView>
|
||||
</MenuOptions>
|
||||
</Menu>
|
||||
);
|
||||
|
@@ -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(() => {
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
|
@@ -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;
|
||||
|