1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-27 10:32:58 +02:00

Merge branch 'master' of github.com:laurent22/joplin

This commit is contained in:
Laurent Cozic 2018-02-14 19:10:42 +00:00
commit cdbb7c4b0d
13 changed files with 133 additions and 75 deletions

View File

@ -15,10 +15,6 @@ class ConfigScreenComponent extends React.Component {
constructor() {
super();
this.state = {
settings: {},
};
shared.init(this);
this.checkSyncConfig_ = async () => {
@ -68,9 +64,7 @@ class ConfigScreenComponent extends React.Component {
};
const updateSettingValue = (key, value) => {
const settings = Object.assign({}, this.state.settings);
settings[key] = Setting.formatValue(key, value);
this.setState({ settings: settings });
return shared.updateSettingValue(this, key, value);
}
// Component key needs to be key+value otherwise it doesn't update when the settings change.
@ -142,10 +136,7 @@ class ConfigScreenComponent extends React.Component {
}
onSaveClick() {
for (let n in this.state.settings) {
if (!this.state.settings.hasOwnProperty(n)) continue;
Setting.setValue(n, this.state.settings[n]);
}
shared.saveSettings(this);
this.props.dispatch({ type: 'NAV_BACK' });
}
@ -167,24 +158,11 @@ class ConfigScreenComponent extends React.Component {
};
const buttonStyle = {
display: this.state.settings === this.props.settings ? 'none' : 'inline-block',
display: this.state.changedSettingKeys.length ? 'inline-block' : 'none',
marginRight: 10,
}
let settingComps = [];
let keys = Setting.keys(true, 'desktop');
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
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);
}
const settingComps = shared.settingsToComponents(this, 'desktop', settings);
const syncTargetMd = SyncTargetRegistry.idToMetadata(settings['sync.target']);

View File

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

View File

@ -1,6 +1,6 @@
{
"name": "Joplin",
"version": "1.0.62",
"version": "1.0.63",
"description": "Joplin for Desktop",
"main": "main.js",
"scripts": {

View File

@ -110,6 +110,7 @@ Select the "WebDAV" synchronisation target and follow the same instructions as f
Known compatible services that use WebDAV:
- [Box.com](https://www.box.com/)
- [DriveHQ](https://www.drivehq.com)
- [Zimbra](https://www.zimbra.com/)

View File

@ -244,7 +244,7 @@ The following commands are available in [command-line mode](#command-line-mode):
Possible values: HH:mm (20:30), h:mm A (8:30 PM).
Default: "HH:mm"
uncompletedTodosOnTop Show uncompleted todos on top of the lists.
uncompletedTodosOnTop Show uncompleted to-dos on top of the lists.
Type: bool.
Default: true

View File

@ -133,6 +133,42 @@ class WebDavApi {
return this.valueFromJson(json, keys, 'array');
}
resourcePropByName(resource, outputType, propName) {
const propStats = resource['d:propstat'];
let output = null;
if (!Array.isArray(propStats)) throw new Error('Missing d:propstat property');
for (let i = 0; i < propStats.length; i++) {
const props = propStats[i]['d:prop'];
if (!Array.isArray(props) || !props.length) continue;
const prop = props[0];
if (Array.isArray(prop[propName])) {
output = prop[propName];
break;
}
}
if (outputType === 'string') {
// If the XML has not attribute the value is directly a string
// If the XML node has attributes, the value is under "_".
// Eg for this XML, the string will be under {"_":"Thu, 01 Feb 2018 17:24:05 GMT"}:
// <a:getlastmodified b:dt="dateTime.rfc1123">Thu, 01 Feb 2018 17:24:05 GMT</a:getlastmodified>
// For this XML, the value will be "Thu, 01 Feb 2018 17:24:05 GMT"
// <a:getlastmodified>Thu, 01 Feb 2018 17:24:05 GMT</a:getlastmodified>
output = output[0];
if (typeof output === 'object' && '_' in output) output = output['_'];
if (typeof output !== 'string') return null;
return output;
}
if (outputType === 'array') {
return output;
}
throw new Error('Invalid output type: ' + outputType);
}
async execPropFind(path, depth, fields = null, options = null) {
if (fields === null) fields = ['d:getlastmodified'];

View File

@ -20,11 +20,6 @@ class ConfigScreenComponent extends BaseScreenComponent {
super();
this.styles_ = {};
this.state = {
settings: {},
settingsChanged: false,
};
shared.init(this);
this.checkSyncConfig_ = async () => {
@ -32,11 +27,7 @@ class ConfigScreenComponent extends BaseScreenComponent {
}
this.saveButton_press = () => {
for (let n in this.state.settings) {
if (!this.state.settings.hasOwnProperty(n)) continue;
Setting.setValue(n, this.state.settings[n]);
}
this.setState({settingsChanged:false});
return shared.saveSettings(this);
};
}
@ -74,6 +65,11 @@ class ConfigScreenComponent extends BaseScreenComponent {
fontSize: theme.fontSize,
flex: 1,
},
descriptionText: {
color: theme.color,
fontSize: theme.fontSize,
flex: 1,
},
settingControl: {
color: theme.color,
flex: 1,
@ -113,12 +109,7 @@ class ConfigScreenComponent extends BaseScreenComponent {
let output = null;
const updateSettingValue = (key, value) => {
const settings = Object.assign({}, this.state.settings);
settings[key] = Setting.formatValue(key, value);
this.setState({
settings: settings,
settingsChanged: true,
});
return shared.updateSettingValue(this, key, value);
}
const md = Setting.settingMetadata(key);
@ -187,20 +178,7 @@ class ConfigScreenComponent extends BaseScreenComponent {
render() {
const settings = this.state.settings;
const keys = Setting.keys(true, 'mobile');
let settingComps = [];
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
//if (key == 'sync.target' && !settings.showAdvancedOptions) continue;
if (!Setting.isPublic(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);
}
const settingComps = shared.settingsToComponents(this, 'mobile', settings);
const syncTargetMd = SyncTargetRegistry.idToMetadata(settings['sync.target']);
@ -208,8 +186,8 @@ class ConfigScreenComponent extends BaseScreenComponent {
const messages = shared.checkSyncConfigMessages(this);
const statusComp = !messages.length ? null : (
<View style={{flex:1, marginTop: 10}}>
<Text>{messages[0]}</Text>
{messages.length >= 1 ? (<Text style={{marginTop:10}}>{messages[1]}</Text>) : null}
<Text style={this.styles().descriptionText}>{messages[0]}</Text>
{messages.length >= 1 ? (<View style={{marginTop:10}}><Text style={this.styles().descriptionText}>{messages[1]}</Text></View>) : null}
</View>);
settingComps.push(
@ -244,7 +222,7 @@ class ConfigScreenComponent extends BaseScreenComponent {
<ScreenHeader
title={_('Configuration')}
showSaveButton={true}
saveButtonDisabled={!this.state.settingsChanged}
saveButtonDisabled={!this.state.changedSettingKeys.length}
onSaveButtonPress={this.saveButton_press}
/>
<ScrollView >

View File

@ -7,6 +7,8 @@ const shared = {}
shared.init = function(comp) {
if (!comp.state) comp.state = {};
comp.state.checkSyncConfigResult = null;
comp.state.settings = {};
comp.state.changedSettingKeys = [];
}
shared.checkSyncConfig = async function(comp, settings) {
@ -34,4 +36,46 @@ shared.checkSyncConfigMessages = function(comp) {
return output;
}
shared.updateSettingValue = function(comp, key, value) {
const settings = Object.assign({}, comp.state.settings);
const changedSettingKeys = comp.state.changedSettingKeys.slice();
settings[key] = Setting.formatValue(key, value);
if (changedSettingKeys.indexOf(key) < 0) changedSettingKeys.push(key);
comp.setState({
settings: settings,
changedSettingKeys: changedSettingKeys,
});
}
shared.saveSettings = function(comp) {
for (let key in comp.state.settings) {
if (!comp.state.settings.hasOwnProperty(key)) continue;
if (comp.state.changedSettingKeys.indexOf(key) < 0) continue;
console.info("Saving", key, comp.state.settings[key]);
Setting.setValue(key, comp.state.settings[key]);
}
comp.setState({ changedSettingKeys: [] });
}
shared.settingsToComponents = function(comp, device, settings) {
const keys = Setting.keys(true, device);
const settingComps = [];
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
if (!Setting.isPublic(key)) continue;
const md = Setting.settingMetadata(key);
if (md.show && !md.show(settings)) continue;
const settingComp = comp.settingToComponent(key, settings[key]);
if (!settingComp) continue;
settingComps.push(settingComp);
}
return settingComps
}
module.exports = shared;

View File

@ -27,7 +27,7 @@ class FileApiDriverWebDav {
const result = await this.api().execPropFind(path, 0, [
'd:getlastmodified',
'd:resourcetype',
'd:getcontentlength', // Remove this once PUT call issue is sorted out
// 'd:getcontentlength', // Remove this once PUT call issue is sorted out
]);
const resource = this.api().objectFromJson(result, ['d:multistatus', 'd:response', 0]);
@ -39,22 +39,40 @@ class FileApiDriverWebDav {
}
statFromResource_(resource, path) {
const isCollection = this.api().stringFromJson(resource, ['d:propstat', 0, 'd:prop', 0, 'd:resourcetype', 0, 'd:collection', 0]);
const lastModifiedString = this.api().stringFromJson(resource, ['d:propstat', 0, 'd:prop', 0, 'd:getlastmodified', 0]);
// WebDAV implementations are always slighly different from one server to another but, at the minimum,
// a resource should have a propstat key - if not it's probably an error.
const propStat = this.api().arrayFromJson(resource, ['d:propstat']);
if (!Array.isArray(propStat)) throw new Error('Invalid WebDAV resource format: ' + JSON.stringify(resource));
const resourceTypes = this.api().resourcePropByName(resource, 'array', 'd:resourcetype');
let isDir = false;
if (Array.isArray(resourceTypes)) {
for (let i = 0; i < resourceTypes.length; i++) {
const t = resourceTypes[i];
if (typeof t === 'object' && 'd:collection' in t) {
isDir = true;
break;
}
}
}
const lastModifiedString = this.api().resourcePropByName(resource, 'string', 'd:getlastmodified');
// const sizeDONOTUSE = Number(this.api().stringFromJson(resource, ['d:propstat', 0, 'd:prop', 0, 'd:getcontentlength', 0]));
// if (isNaN(sizeDONOTUSE)) throw new Error('Cannot get content size: ' + JSON.stringify(resource));
if (!lastModifiedString) throw new Error('Could not get lastModified date: ' + JSON.stringify(resource));
const lastModifiedDate = new Date(lastModifiedString);
// Note: Not all WebDAV servers return a getlastmodified date (eg. Seafile, which doesn't return the
// property for folders) so we can only throw an error if it's a file.
if (!lastModifiedString && !isDir) throw new Error('Could not get lastModified date for resource: ' + JSON.stringify(resource));
const lastModifiedDate = lastModifiedString ? new Date(lastModifiedString) : new Date();
if (isNaN(lastModifiedDate.getTime())) throw new Error('Invalid date: ' + lastModifiedString);
return {
path: path,
// created_time: lastModifiedDate.getTime(),
updated_time: lastModifiedDate.getTime(),
isDir: isCollection === '',
isDir: isDir,
// sizeDONOTUSE: sizeDONOTUSE, // This property is used only for the WebDAV PUT hack (see below) so mark it as such so that it can be removed with the hack later on.
};
}
@ -260,7 +278,7 @@ class FileApiDriverWebDav {
]);
const resources = this.api().arrayFromJson(result, ['d:multistatus', 'd:response']);
const stats = this.statsFromResources_(resources)
const stats = this.statsFromResources_(resources);
return {
items: stats,

View File

@ -58,7 +58,7 @@ class Setting extends BaseModel {
// recent: _('Non-completed and recently completed ones'),
// nonCompleted: _('Non-completed ones only'),
// })},
'uncompletedTodosOnTop': { value: true, type: Setting.TYPE_BOOL, public: true, label: () => _('Show uncompleted todos on top of the lists') },
'uncompletedTodosOnTop': { value: true, type: Setting.TYPE_BOOL, public: true, label: () => _('Show uncompleted to-dos on top of the lists') },
'trackLocation': { value: true, type: Setting.TYPE_BOOL, public: true, label: () => _('Save geo-location with notes') },
'newTodoFocus': { value: 'title', type: Setting.TYPE_STRING, isEnum: true, public: true, appTypes: ['desktop'], label: () => _('When creating a new to-do:'), options: () => {
return {

View File

@ -310,6 +310,7 @@ sudo ln -s ~/.joplin-bin/bin/joplin /usr/bin/joplin
<p>Select the &quot;WebDAV&quot; synchronisation target and follow the same instructions as for Nextcloud above.</p>
<p>Known compatible services that use WebDAV:</p>
<ul>
<li><a href="https://www.box.com/">Box.com</a></li>
<li><a href="https://www.drivehq.com">DriveHQ</a></li>
<li><a href="https://www.zimbra.com/">Zimbra</a></li>
</ul>
@ -390,14 +391,14 @@ $$
<td><img src="https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/hr.png" alt=""></td>
<td>Croatian</td>
<td>hr_HR</td>
<td>Hrvoje Mandić <a href="&#x6d;&#x61;&#105;&#x6c;&#x74;&#111;&#x3a;&#116;&#114;&#98;&#x75;&#104;&#x6f;&#x6d;&#x40;&#110;&#101;&#116;&#46;&#x68;&#x72;">&#116;&#114;&#98;&#x75;&#104;&#x6f;&#x6d;&#x40;&#110;&#101;&#116;&#46;&#x68;&#x72;</a></td>
<td>Hrvoje Mandić <a href="&#109;&#x61;&#105;&#108;&#116;&#x6f;&#58;&#x74;&#114;&#x62;&#x75;&#104;&#x6f;&#x6d;&#x40;&#x6e;&#101;&#x74;&#46;&#104;&#x72;">&#x74;&#114;&#x62;&#x75;&#104;&#x6f;&#x6d;&#x40;&#x6e;&#101;&#x74;&#46;&#104;&#x72;</a></td>
<td>72%</td>
</tr>
<tr>
<td><img src="https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/de.png" alt=""></td>
<td>Deutsch</td>
<td>de_DE</td>
<td>Tobias Strobel <a href="&#109;&#97;&#x69;&#x6c;&#116;&#111;&#58;&#x67;&#x69;&#116;&#64;&#x73;&#x74;&#114;&#111;&#x62;&#x65;&#x6c;&#x74;&#111;&#x62;&#x69;&#97;&#x73;&#x2e;&#100;&#101;">&#x67;&#x69;&#116;&#64;&#x73;&#x74;&#114;&#111;&#x62;&#x65;&#x6c;&#x74;&#111;&#x62;&#x69;&#97;&#x73;&#x2e;&#100;&#101;</a></td>
<td>Tobias Strobel <a href="&#x6d;&#x61;&#105;&#108;&#x74;&#x6f;&#x3a;&#x67;&#x69;&#x74;&#x40;&#x73;&#x74;&#x72;&#111;&#x62;&#x65;&#108;&#116;&#x6f;&#x62;&#105;&#x61;&#115;&#46;&#100;&#101;">&#x67;&#x69;&#x74;&#x40;&#x73;&#x74;&#x72;&#111;&#x62;&#x65;&#108;&#116;&#x6f;&#x62;&#105;&#x61;&#115;&#46;&#100;&#101;</a></td>
<td>91%</td>
</tr>
<tr>
@ -453,14 +454,14 @@ $$
<td><img src="https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/ru.png" alt=""></td>
<td>Русский</td>
<td>ru_RU</td>
<td>Artyom Karlov <a href="&#109;&#97;&#105;&#108;&#x74;&#x6f;&#x3a;&#97;&#114;&#116;&#x79;&#x6f;&#109;&#46;&#107;&#97;&#x72;&#x6c;&#x6f;&#x76;&#x40;&#103;&#109;&#97;&#105;&#108;&#46;&#99;&#111;&#x6d;">&#97;&#114;&#116;&#x79;&#x6f;&#109;&#46;&#107;&#97;&#x72;&#x6c;&#x6f;&#x76;&#x40;&#103;&#109;&#97;&#105;&#108;&#46;&#99;&#111;&#x6d;</a></td>
<td>Artyom Karlov <a href="&#109;&#x61;&#105;&#x6c;&#116;&#111;&#58;&#97;&#x72;&#116;&#121;&#x6f;&#109;&#x2e;&#107;&#97;&#114;&#x6c;&#111;&#118;&#x40;&#x67;&#109;&#x61;&#x69;&#x6c;&#x2e;&#99;&#111;&#x6d;">&#97;&#x72;&#116;&#121;&#x6f;&#109;&#x2e;&#107;&#97;&#114;&#x6c;&#111;&#118;&#x40;&#x67;&#109;&#x61;&#x69;&#x6c;&#x2e;&#99;&#111;&#x6d;</a></td>
<td>94%</td>
</tr>
<tr>
<td><img src="https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/cn.png" alt=""></td>
<td>中文 (简体)</td>
<td>zh_CN</td>
<td>RCJacH <a href="&#x6d;&#x61;&#x69;&#x6c;&#116;&#x6f;&#58;&#82;&#x43;&#74;&#97;&#99;&#72;&#64;&#x6f;&#117;&#x74;&#x6c;&#111;&#x6f;&#107;&#46;&#99;&#111;&#x6d;">&#82;&#x43;&#74;&#97;&#99;&#72;&#64;&#x6f;&#117;&#x74;&#x6c;&#111;&#x6f;&#107;&#46;&#99;&#111;&#x6d;</a></td>
<td>RCJacH <a href="&#x6d;&#97;&#105;&#x6c;&#116;&#111;&#x3a;&#82;&#x43;&#x4a;&#97;&#99;&#72;&#64;&#x6f;&#117;&#x74;&#108;&#x6f;&#111;&#x6b;&#46;&#x63;&#111;&#x6d;">&#82;&#x43;&#x4a;&#97;&#99;&#72;&#64;&#x6f;&#117;&#x74;&#108;&#x6f;&#111;&#x6b;&#46;&#x63;&#111;&#x6d;</a></td>
<td>75%</td>
</tr>
<tr>

View File

@ -395,7 +395,7 @@ Possible keys/values:
Possible values: HH:mm (20:30), h:mm A (8:30 PM).
Default: &quot;HH:mm&quot;
uncompletedTodosOnTop Show uncompleted todos on top of the lists.
uncompletedTodosOnTop Show uncompleted to-dos on top of the lists.
Type: bool.
Default: true

View File

@ -18,6 +18,8 @@
"*.min.js",
"ElectronClient/app/gui/note-viewer/highlight/*.pack.js",
"ElectronClient/app/css/font-awesome.min.css",
"docs/*.html",
"docs/*.svg",
],
"folder_exclude_patterns":
[