diff --git a/CliClient/app/command-ls.js b/CliClient/app/command-ls.js
index 0e8a96460..9cae6e2d3 100644
--- a/CliClient/app/command-ls.js
+++ b/CliClient/app/command-ls.js
@@ -36,7 +36,6 @@ class Command extends BaseCommand {
async action(args) {
let pattern = args['pattern'];
- let suffix = '';
let items = [];
let options = args.options;
@@ -59,7 +58,6 @@ class Command extends BaseCommand {
if (pattern == '/' || !app().currentFolder()) {
queryOptions.includeConflictFolder = true;
items = await Folder.all(queryOptions);
- suffix = '/';
modelType = Folder.modelType();
} else {
if (!app().currentFolder()) throw new Error(_('Please select a notebook first.'));
@@ -97,7 +95,7 @@ class Command extends BaseCommand {
row.push(time.unixMsToLocalDateTime(item.updated_time));
}
- let title = item.title + suffix;
+ let title = item.title;
if (!shortIdShown && (seenTitles.indexOf(item.title) >= 0 || !item.title)) {
title += ' (' + BaseModel.shortId(item.id) + ')';
} else {
diff --git a/CliClient/locales/en_GB.po b/CliClient/locales/en_GB.po
index ee9174741..c8dab0eeb 100644
--- a/CliClient/locales/en_GB.po
+++ b/CliClient/locales/en_GB.po
@@ -318,6 +318,11 @@ msgstr ""
msgid "Please open this URL in your browser to authenticate the application:"
msgstr ""
+msgid ""
+"Cannot refresh token: authentication data is missing. Starting the "
+"synchronisation again may fix the problem."
+msgstr ""
+
msgid ""
"Please set the \"sync.2.path\" config value to the desired synchronisation "
"destination."
diff --git a/CliClient/locales/fr_FR.po b/CliClient/locales/fr_FR.po
index 91f0b5c6e..30888c1d0 100644
--- a/CliClient/locales/fr_FR.po
+++ b/CliClient/locales/fr_FR.po
@@ -14,6 +14,8 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.0.3\n"
+"POT-Creation-Date: \n"
+"PO-Revision-Date: \n"
msgid "No notebook selected."
msgstr "Aucun carnet n'est sélectionné."
@@ -353,6 +355,11 @@ msgstr ""
"Veuillez ouvrir cette URL dans votre navigateur internet pour autoriser le "
"logiciel :"
+msgid ""
+"Cannot refresh token: authentication data is missing. Starting the "
+"synchronisation again may fix the problem."
+msgstr ""
+
msgid ""
"Please set the \"sync.2.path\" config value to the desired synchronisation "
"destination."
@@ -512,7 +519,7 @@ msgstr "Nouveau carnet"
msgid "There are currently no notes. Create one by clicking on the (+) button."
msgstr ""
-"Ce carnet ne contient aucune note. Créez-en une en cliquant sur le bouton "
+"Ce carnet ne contient aucune note. Créez-en une en appuyant sur le bouton "
"(+)."
msgid "Log"
@@ -583,8 +590,8 @@ msgid ""
"Click on the (+) button to create a new note or notebook. Click on the side "
"menu to access your existing notebooks."
msgstr ""
-"Cliquez sur le bouton (+) pour une nouvelle note ou carnet. Ouvrez le menu "
-"latéral pour accéder à vos carnets."
+"Appuyez sur le bouton (+) pour créer une nouvelle note ou carnet. Ouvrez le "
+"menu latéral pour accéder à vos carnets."
msgid "You currently have no notebook. Create one by clicking on (+) button."
msgstr ""
diff --git a/CliClient/locales/joplin.pot b/CliClient/locales/joplin.pot
index ee9174741..c8dab0eeb 100644
--- a/CliClient/locales/joplin.pot
+++ b/CliClient/locales/joplin.pot
@@ -318,6 +318,11 @@ msgstr ""
msgid "Please open this URL in your browser to authenticate the application:"
msgstr ""
+msgid ""
+"Cannot refresh token: authentication data is missing. Starting the "
+"synchronisation again may fix the problem."
+msgstr ""
+
msgid ""
"Please set the \"sync.2.path\" config value to the desired synchronisation "
"destination."
diff --git a/ReactNativeClient/lib/components/note-body-viewer.js b/ReactNativeClient/lib/components/note-body-viewer.js
new file mode 100644
index 000000000..c8ad4046a
--- /dev/null
+++ b/ReactNativeClient/lib/components/note-body-viewer.js
@@ -0,0 +1,189 @@
+import React, { Component } from 'react';
+import { WebView, View } from 'react-native';
+import { globalStyle } from 'lib/components/global-style.js';
+import { Resource } from 'lib/models/resource.js';
+import { shim } from 'lib/shim.js';
+import marked from 'lib/marked.js';
+const Entities = require('html-entities').AllHtmlEntities;
+const htmlentities = (new Entities()).encode;
+
+class NoteBodyViewer extends Component {
+
+ constructor() {
+ super();
+ this.state = {
+ resources: {},
+ }
+ }
+
+ async loadResource(id) {
+ const resource = await Resource.load(id);
+ resource.base64 = await shim.readLocalFileBase64(Resource.fullPath(resource));
+
+ let newResources = Object.assign({}, this.state.resources);
+ newResources[id] = resource;
+ this.setState({ resources: newResources });
+ }
+
+ toggleTickAt(body, index) {
+ let counter = -1;
+ while (body.indexOf('- [ ]') >= 0 || body.indexOf('- [X]') >= 0) {
+ counter++;
+
+ body = body.replace(/- \[(X| )\]/, function(v, p1) {
+ let s = p1 == ' ' ? 'NOTICK' : 'TICK';
+ if (index == counter) {
+ s = s == 'NOTICK' ? 'TICK' : 'NOTICK';
+ }
+ return '°°JOP°CHECKBOX°' + s + '°°';
+ });
+ }
+
+ body = body.replace(/°°JOP°CHECKBOX°NOTICK°°/g, '- [ ]');
+ body = body.replace(/°°JOP°CHECKBOX°TICK°°/g, '- [X]');
+
+ return body;
+ }
+
+ markdownToHtml (body, style) {
+ // https://necolas.github.io/normalize.css/
+ const normalizeCss = `
+ html{line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}
+ article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}
+ pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent;-webkit-text-decoration-skip:objects}
+ b,strong{font-weight:bolder}small{font-size:80%}img{border-style:none}
+ `;
+
+ const css = `
+ body {
+ font-size: ` + style.htmlFontSize + `;
+ color: ` + style.htmlColor + `;
+ }
+ h1 {
+ font-size: 1.2em;
+ font-weight: bold;
+ }
+ h2 {
+ font-size: 1em;
+ font-weight: bold;
+ }
+ li {
+
+ }
+ ul {
+ padding-left: 1em;
+ }
+ a.checkbox {
+ font-size: 1.4em;
+ position: relative;
+ top: 0.1em;
+ text-decoration: none;
+ color: ` + style.htmlColor + `;
+ }
+ table {
+ border-collapse: collapse;
+ }
+ td, th {
+ border: 1px solid silver;
+ padding: .5em 1em .5em 1em;
+ }
+ hr {
+ border: 1px solid ` + style.htmlDividerColor + `;
+ }
+ img {
+ width: 100%;
+ }
+ `;
+
+ let counter = -1;
+ while (body.indexOf('- [ ]') >= 0 || body.indexOf('- [X]') >= 0) {
+ body = body.replace(/- \[(X| )\]/, function(v, p1) {
+ let s = p1 == ' ' ? 'NOTICK' : 'TICK';
+ counter++;
+ return '°°JOP°CHECKBOX°' + s + '°' + counter + '°°';
+ });
+ }
+
+ const renderer = new marked.Renderer();
+
+ renderer.link = function (href, title, text) {
+ if (Resource.isResourceUrl(href)) {
+ return '[Resource not yet supported: ' + htmlentities(text) + ']';
+ } else {
+ const js = "postMessage(" + JSON.stringify(href) + "); return false;";
+ let output = "" + htmlentities(text) + '';
+ return output;
+ }
+ }
+
+ renderer.image = (href, title, text) => {
+ const resourceId = Resource.urlToId(href);
+ if (!this.state.resources[resourceId]) {
+ this.loadResource(resourceId);
+ return '';
+ }
+
+ const r = this.state.resources[resourceId];
+ if (r.mime == 'image/png' || r.mime == 'image/jpg' || r.mime == 'image/gif') {
+ const src = 'data:' + r.mime + ';base64,' + r.base64;
+ let output = '';
+ return output;
+ }
+
+ return '[Image: ' + htmlentities(r.title) + '(' + htmlentities(r.mime) + ')]';
+ }
+
+ let html = body ? '' + marked(body, { gfm: true, breaks: true, renderer: renderer }) : '';
+
+ let elementId = 1;
+ while (html.indexOf('°°JOP°') >= 0) {
+ html = html.replace(/°°JOP°CHECKBOX°([A-Z]+)°(\d+)°°/, function(v, type, index) {
+ const js = "postMessage('checkboxclick:" + type + ':' + index + "'); this.textContent = this.textContent == '☐' ? '☑' : '☐'; return false;";
+ return '' + (type == 'NOTICK' ? '☐' : '☑') + '';
+ });
+ }
+
+ let scriptHtml = '';
+
+ html = '
' + html + scriptHtml + '';
+
+ // console.info(html);
+
+ return html;
+ }
+
+ render() {
+ const note = this.props.note;
+ const style = this.props.style;
+ const onCheckboxChange = this.props.onCheckboxChange;
+
+ return (
+
+ {
+ let msg = event.nativeEvent.data;
+
+ //reg.logger().info('postMessage received: ' + msg);
+
+ if (msg.indexOf('checkboxclick:') === 0) {
+ msg = msg.split(':');
+ let index = Number(msg[msg.length - 1]);
+ let currentState = msg[msg.length - 2]; // Not really needed but keep it anyway
+ const newBody = this.toggleTickAt(note.body, index);
+ if (onCheckboxChange) onCheckboxChange(newBody);
+ } else if (msg.indexOf('bodyscroll:') === 0) {
+ msg = msg.split(':');
+ this.bodyScrollTop_ = Number(msg[1]);
+ } else {
+ Linking.openURL(msg);
+ }
+ }}
+ />
+
+ );
+ }
+
+}
+
+export { NoteBodyViewer };
\ No newline at end of file
diff --git a/ReactNativeClient/lib/components/note-item.js b/ReactNativeClient/lib/components/note-item.js
index 56dff02ae..7d9d00dbb 100644
--- a/ReactNativeClient/lib/components/note-item.js
+++ b/ReactNativeClient/lib/components/note-item.js
@@ -17,9 +17,11 @@ let styles = {
borderBottomColor: globalStyle.dividerColor,
alignItems: 'center',
paddingLeft: globalStyle.marginLeft,
+ paddingRight: globalStyle.marginRight,
backgroundColor: globalStyle.backgroundColor,
},
listItemText: {
+ flex: 1,
color: globalStyle.color,
},
};
@@ -70,7 +72,7 @@ class NoteItemComponent extends Component {
const listItemStyle = !!Number(note.is_todo) && checkboxChecked ? styles.listItemFadded : styles.listItem;
return (
- this.onPress()} underlayColor="#0066FF">
+ this.onPress()} underlayColor="#0066FF">
{
@@ -237,10 +233,10 @@ class NoteScreenComponent extends BaseScreenComponent {
}
- async toggleIsTodo_onPress() {
- let note = await Note.toggleIsTodo(this.state.note.id);
- let newState = { note: note };
- if (!note.id) newState.lastSavedNote = Object.assign({}, note);
+ toggleIsTodo_onPress() {
+ let newNote = Note.toggleIsTodo(this.state.note);
+ let newState = { note: newNote };
+ //if (!newNote.id) newState.lastSavedNote = Object.assign({}, newNote);
this.setState(newState);
}
@@ -249,15 +245,6 @@ class NoteScreenComponent extends BaseScreenComponent {
this.refreshNoteMetadata(true);
}
- async loadResource(id) {
- const resource = await Resource.load(id);
- resource.base64 = await shim.readLocalFileBase64(Resource.fullPath(resource));
-
- let newResources = Object.assign({}, this.state.resources);
- newResources[id] = resource;
- this.setState({ resources: newResources });
- }
-
async showOnMap_onPress() {
if (!this.state.note.id) return;
@@ -302,158 +289,11 @@ class NoteScreenComponent extends BaseScreenComponent {
let bodyComponent = null;
if (this.state.mode == 'view') {
- function toggleTickAt(body, index) {
- let counter = -1;
- while (body.indexOf('- [ ]') >= 0 || body.indexOf('- [X]') >= 0) {
- counter++;
+ const onCheckboxChange = (newBody) => {
+ this.saveOneProperty('body', newBody);
+ };
- body = body.replace(/- \[(X| )\]/, function(v, p1) {
- let s = p1 == ' ' ? 'NOTICK' : 'TICK';
- if (index == counter) {
- s = s == 'NOTICK' ? 'TICK' : 'NOTICK';
- }
- return '°°JOP°CHECKBOX°' + s + '°°';
- });
- }
-
- body = body.replace(/°°JOP°CHECKBOX°NOTICK°°/g, '- [ ]');
- body = body.replace(/°°JOP°CHECKBOX°TICK°°/g, '- [X]');
-
- return body;
- }
-
- const markdownToHtml = (body, style) => {
- // https://necolas.github.io/normalize.css/
- const normalizeCss = `
- html{line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}
- article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}
- pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent;-webkit-text-decoration-skip:objects}
- b,strong{font-weight:bolder}small{font-size:80%}img{border-style:none}
- `;
-
- const css = `
- body {
- font-size: ` + style.htmlFontSize + `;
- color: ` + style.htmlColor + `;
- }
- h1 {
- font-size: 1.2em;
- font-weight: bold;
- }
- h2 {
- font-size: 1em;
- font-weight: bold;
- }
- li {
-
- }
- ul {
- padding-left: 1em;
- }
- a.checkbox {
- font-size: 1.4em;
- position: relative;
- top: 0.1em;
- text-decoration: none;
- color: ` + style.htmlColor + `;
- }
- table {
- border-collapse: collapse;
- }
- td, th {
- border: 1px solid silver;
- padding: .5em 1em .5em 1em;
- }
- hr {
- border: 1px solid ` + style.htmlDividerColor + `;
- }
- img {
- width: 100%;
- }
- `;
-
- let counter = -1;
- while (body.indexOf('- [ ]') >= 0 || body.indexOf('- [X]') >= 0) {
- body = body.replace(/- \[(X| )\]/, function(v, p1) {
- let s = p1 == ' ' ? 'NOTICK' : 'TICK';
- counter++;
- return '°°JOP°CHECKBOX°' + s + '°' + counter + '°°';
- });
- }
-
- const renderer = new marked.Renderer();
-
- renderer.link = function (href, title, text) {
- if (Resource.isResourceUrl(href)) {
- return '[Resource not yet supported: ' + htmlentities(text) + ']';
- } else {
- const js = "postMessage(" + JSON.stringify(href) + "); return false;";
- let output = "" + htmlentities(text) + '';
- return output;
- }
- }
-
- renderer.image = (href, title, text) => {
- const resourceId = Resource.urlToId(href);
- if (!this.state.resources[resourceId]) {
- this.loadResource(resourceId);
- return '';
- }
-
- const r = this.state.resources[resourceId];
- if (r.mime == 'image/png' || r.mime == 'image/jpg' || r.mime == 'image/gif') {
- const src = 'data:' + r.mime + ';base64,' + r.base64;
- let output = '';
- return output;
- }
-
- return '[Image: ' + htmlentities(r.title) + '(' + htmlentities(r.mime) + ')]';
- }
-
- let html = note ? '' + marked(body, { gfm: true, breaks: true, renderer: renderer }) : '';
-
- let elementId = 1;
- while (html.indexOf('°°JOP°') >= 0) {
- html = html.replace(/°°JOP°CHECKBOX°([A-Z]+)°(\d+)°°/, function(v, type, index) {
- const js = "postMessage('checkboxclick:" + type + ':' + index + "'); this.textContent = this.textContent == '☐' ? '☑' : '☐'; return false;";
- return '' + (type == 'NOTICK' ? '☐' : '☑') + '';
- });
- }
-
- let scriptHtml = '';
-
- html = '' + html + scriptHtml + '';
-
- console.info(html);
-
- return html;
- }
-
- bodyComponent = (
-
- {
- let msg = event.nativeEvent.data;
-
- //reg.logger().info('postMessage received: ' + msg);
-
- if (msg.indexOf('checkboxclick:') === 0) {
- msg = msg.split(':');
- let index = Number(msg[msg.length - 1]);
- let currentState = msg[msg.length - 2]; // Not really needed but keep it anyway
- const newBody = toggleTickAt(note.body, index);
- this.saveOneProperty('body', newBody);
- } else if (msg.indexOf('bodyscroll:') === 0) {
- msg = msg.split(':');
- this.bodyScrollTop_ = Number(msg[1]);
- } else {
- Linking.openURL(msg);
- }
- }}
- />
-
- );
+ bodyComponent = { onCheckboxChange(newBody) }}/>
} else {
const focusBody = !isNew && !!note.title;
bodyComponent = (
diff --git a/ReactNativeClient/lib/models/note.js b/ReactNativeClient/lib/models/note.js
index 63a33b8cd..919f1d662 100644
--- a/ReactNativeClient/lib/models/note.js
+++ b/ReactNativeClient/lib/models/note.js
@@ -267,15 +267,17 @@ class Note extends BaseItem {
});
}
- static async toggleIsTodo(noteId) {
- let note = await Note.load(noteId);
- const isTodo = !note.is_todo ? 1 : 0;
- note.is_todo = isTodo;
- if (!note.is_todo) {
- note.todo_due = 0;
- note.todo_completed = 0;
+ static toggleIsTodo(note) {
+ if (!('is_todo' in note)) throw new Error('Missing "is_todo" property');
+
+ let output = Object.assign({}, note);
+ output.is_todo = output.is_todo ? 0 : 1;
+ if (!output.is_todo) {
+ output.todo_due = 0;
+ output.todo_completed = 0;
}
- return note;
+
+ return output;
}
static async duplicate(noteId, options = null) {
diff --git a/ReactNativeClient/lib/synchronizer.js b/ReactNativeClient/lib/synchronizer.js
index c2db9a430..098deeb6c 100644
--- a/ReactNativeClient/lib/synchronizer.js
+++ b/ReactNativeClient/lib/synchronizer.js
@@ -154,7 +154,9 @@ class Synchronizer {
error.code = 'alreadyStarted';
throw error;
return;
- }
+ }
+
+ this.state_ = 'in_progress';
this.onProgress_ = options.onProgress ? options.onProgress : function(o) {};
this.progressReport_ = { errors: [] };
@@ -175,7 +177,6 @@ class Synchronizer {
let outputContext = Object.assign({}, lastContext);
- this.state_ = 'in_progress';
this.dispatch({ type: 'SYNC_STARTED' });
@@ -463,7 +464,6 @@ class Synchronizer {
this.cancelling_ = false;
}
- this.state_ = 'idle';
this.progressReport_.completedTime = time.unixMs();
@@ -475,6 +475,8 @@ class Synchronizer {
this.progressReport_ = {};
this.dispatch({ type: 'SYNC_COMPLETED' });
+
+ this.state_ = 'idle';
return outputContext;
}